mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 00:39:13 +00:00
fix(web): improve tooltip responsiveness and click-through (#1314)
* fix(web): prevent tooltip click-through on mobile * fix: adjust tooltip placement based on screen size
This commit is contained in:
parent
6815c67e0c
commit
3fd939b531
3 changed files with 77 additions and 16 deletions
|
@ -34,6 +34,7 @@
|
|||
"@headlessui/react": "^1.7.17",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@hookform/error-message": "^2.0.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-query-devtools": "^4.36.1",
|
||||
|
|
3
web/pnpm-lock.yaml
generated
3
web/pnpm-lock.yaml
generated
|
@ -17,6 +17,9 @@ dependencies:
|
|||
'@hookform/error-message':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(react-dom@18.2.0)(react-hook-form@7.48.2)(react@18.2.0)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
'@tailwindcss/forms':
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.7(tailwindcss@3.3.5)
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { usePopperTooltip } from "react-popper-tooltip";
|
||||
import { Placement } from '@popperjs/core';
|
||||
|
||||
import { classNames } from "@utils";
|
||||
|
||||
|
@ -21,6 +24,7 @@ interface TooltipProps {
|
|||
// NOTE(stacksmash76): onClick is not propagated
|
||||
// to the label (always-visible) component, so you will have
|
||||
// to use the `onLabelClick` prop in this case.
|
||||
|
||||
export const Tooltip = ({
|
||||
label,
|
||||
onLabelClick,
|
||||
|
@ -29,22 +33,75 @@ export const Tooltip = ({
|
|||
requiresClick,
|
||||
maxWidth = "max-w-sm"
|
||||
}: TooltipProps) => {
|
||||
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
|
||||
const [tooltipNode, setTooltipNode] = useState<HTMLDivElement | null>(null);
|
||||
const [triggerNode, setTriggerNode] = useState<HTMLDivElement | null>(null);
|
||||
const isTouchDevice = () => {
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
};
|
||||
|
||||
// default tooltip placement to right
|
||||
const [placement, setPlacement] = useState<Placement>('right');
|
||||
|
||||
// check screen size and update placement if needed
|
||||
useEffect(() => {
|
||||
const updatePlacementForScreenSize = () => {
|
||||
const screenWidth = window.innerWidth;
|
||||
if (screenWidth < 640) { // tailwind's sm breakpoint
|
||||
setPlacement('top');
|
||||
} else {
|
||||
setPlacement('right');
|
||||
}
|
||||
};
|
||||
|
||||
updatePlacementForScreenSize();
|
||||
window.addEventListener('resize', updatePlacementForScreenSize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', updatePlacementForScreenSize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
const {
|
||||
// TODO?: getArrowProps,
|
||||
getTooltipProps,
|
||||
setTooltipRef,
|
||||
setTriggerRef,
|
||||
setTooltipRef: popperSetTooltipRef,
|
||||
setTriggerRef: popperSetTriggerRef,
|
||||
visible
|
||||
} = usePopperTooltip({
|
||||
trigger: requiresClick ? ["click"] : ["click", "hover"],
|
||||
trigger: isTouchDevice() ? [] : (requiresClick ? 'click' : ['click', 'hover']),
|
||||
interactive: true,
|
||||
delayHide: 200,
|
||||
placement: "right"
|
||||
placement,
|
||||
});
|
||||
|
||||
if (!children || Array.isArray(children) && !children.length) {
|
||||
return null;
|
||||
}
|
||||
const handleTouch = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsTooltipVisible(!isTooltipVisible);
|
||||
};
|
||||
|
||||
const setTooltipRef = (node: HTMLDivElement | null) => {
|
||||
popperSetTooltipRef(node);
|
||||
setTooltipNode(node);
|
||||
};
|
||||
|
||||
const setTriggerRef = (node: HTMLDivElement | null) => {
|
||||
popperSetTriggerRef(node);
|
||||
setTriggerNode(node);
|
||||
};
|
||||
|
||||
const handleClickOutside = useCallback((event: TouchEvent) => {
|
||||
if (tooltipNode && !tooltipNode.contains(event.target as Node) && triggerNode && !triggerNode.contains(event.target as Node)) {
|
||||
setIsTooltipVisible(false);
|
||||
}
|
||||
}, [tooltipNode, triggerNode]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('touchstart', handleClickOutside, true);
|
||||
return () => {
|
||||
document.removeEventListener('touchstart', handleClickOutside, true);
|
||||
};
|
||||
}, [handleClickOutside, tooltipNode, triggerNode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -52,17 +109,16 @@ export const Tooltip = ({
|
|||
ref={setTriggerRef}
|
||||
className="truncate"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
onLabelClick?.(e);
|
||||
if (!isTouchDevice() && !visible) {
|
||||
onLabelClick?.(e);
|
||||
}
|
||||
}}
|
||||
onTouchStart={isTouchDevice() ? handleTouch : undefined}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
<Transition
|
||||
show={visible}
|
||||
show={isTouchDevice() ? isTooltipVisible : visible}
|
||||
className="z-10"
|
||||
enter="transition duration-200 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
|
@ -77,7 +133,8 @@ export const Tooltip = ({
|
|||
className: classNames(
|
||||
maxWidth,
|
||||
"rounded-md border border-gray-300 text-black text-xs normal-case tracking-normal font-normal shadow-lg dark:text-white dark:border-gray-700 dark:shadow-2xl"
|
||||
)
|
||||
),
|
||||
onClick: (e: React.MouseEvent) => e.stopPropagation()
|
||||
})}
|
||||
>
|
||||
{title ? (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue