mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +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",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@hookform/error-message": "^2.0.1",
|
"@hookform/error-message": "^2.0.1",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^4.36.1",
|
||||||
"@tanstack/react-query-devtools": "^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':
|
'@hookform/error-message':
|
||||||
specifier: ^2.0.1
|
specifier: ^2.0.1
|
||||||
version: 2.0.1(react-dom@18.2.0)(react-hook-form@7.48.2)(react@18.2.0)
|
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':
|
'@tailwindcss/forms':
|
||||||
specifier: ^0.5.7
|
specifier: ^0.5.7
|
||||||
version: 0.5.7(tailwindcss@3.3.5)
|
version: 0.5.7(tailwindcss@3.3.5)
|
||||||
|
|
|
@ -3,9 +3,12 @@
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
* 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 { Transition } from "@headlessui/react";
|
||||||
import { usePopperTooltip } from "react-popper-tooltip";
|
import { usePopperTooltip } from "react-popper-tooltip";
|
||||||
|
import { Placement } from '@popperjs/core';
|
||||||
|
|
||||||
import { classNames } from "@utils";
|
import { classNames } from "@utils";
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ interface TooltipProps {
|
||||||
// NOTE(stacksmash76): onClick is not propagated
|
// NOTE(stacksmash76): onClick is not propagated
|
||||||
// to the label (always-visible) component, so you will have
|
// to the label (always-visible) component, so you will have
|
||||||
// to use the `onLabelClick` prop in this case.
|
// to use the `onLabelClick` prop in this case.
|
||||||
|
|
||||||
export const Tooltip = ({
|
export const Tooltip = ({
|
||||||
label,
|
label,
|
||||||
onLabelClick,
|
onLabelClick,
|
||||||
|
@ -29,22 +33,75 @@ export const Tooltip = ({
|
||||||
requiresClick,
|
requiresClick,
|
||||||
maxWidth = "max-w-sm"
|
maxWidth = "max-w-sm"
|
||||||
}: TooltipProps) => {
|
}: 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 {
|
const {
|
||||||
// TODO?: getArrowProps,
|
|
||||||
getTooltipProps,
|
getTooltipProps,
|
||||||
setTooltipRef,
|
setTooltipRef: popperSetTooltipRef,
|
||||||
setTriggerRef,
|
setTriggerRef: popperSetTriggerRef,
|
||||||
visible
|
visible
|
||||||
} = usePopperTooltip({
|
} = usePopperTooltip({
|
||||||
trigger: requiresClick ? ["click"] : ["click", "hover"],
|
trigger: isTouchDevice() ? [] : (requiresClick ? 'click' : ['click', 'hover']),
|
||||||
interactive: true,
|
interactive: true,
|
||||||
delayHide: 200,
|
delayHide: 200,
|
||||||
placement: "right"
|
placement,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!children || Array.isArray(children) && !children.length) {
|
const handleTouch = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
return null;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -52,17 +109,16 @@ export const Tooltip = ({
|
||||||
ref={setTriggerRef}
|
ref={setTriggerRef}
|
||||||
className="truncate"
|
className="truncate"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
if (!isTouchDevice() && !visible) {
|
||||||
e.stopPropagation();
|
onLabelClick?.(e);
|
||||||
e.nativeEvent.stopImmediatePropagation();
|
}
|
||||||
|
|
||||||
onLabelClick?.(e);
|
|
||||||
}}
|
}}
|
||||||
|
onTouchStart={isTouchDevice() ? handleTouch : undefined}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
show={visible}
|
show={isTouchDevice() ? isTooltipVisible : visible}
|
||||||
className="z-10"
|
className="z-10"
|
||||||
enter="transition duration-200 ease-out"
|
enter="transition duration-200 ease-out"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
|
@ -77,7 +133,8 @@ export const Tooltip = ({
|
||||||
className: classNames(
|
className: classNames(
|
||||||
maxWidth,
|
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"
|
"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 ? (
|
{title ? (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue