autobrr/web/src/components/tooltips/Tooltip.tsx
ze0s 0d53f7e5fc
feat(download-clients): rtorrent support Digest Auth (#1596)
* feat(download-clients): rtorrent support basic auth

* feat(download-client): implement new auth logic

* fix(download-client): tests store

* chore(deps): update go-rtorrent to v1.11.0
2024-08-28 12:21:56 +02:00

154 lines
4.6 KiB
TypeScript

/*
* Copyright (c) 2021 - 2024, Ludvig Lundgren and the autobrr contributors.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
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";
interface TooltipProps {
label: ReactNode;
title?: ReactNode;
maxWidth?: string;
requiresClick?: boolean;
children: ReactNode;
}
// 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,
title,
children,
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);
// 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 {
getTooltipProps,
setTooltipRef: popperSetTooltipRef,
setTriggerRef: popperSetTriggerRef,
visible
} = usePopperTooltip({
trigger: requiresClick ? 'click' : ['click', 'hover'],
interactive: true,
delayHide: 200,
placement,
followCursor: placement === "right"
});
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setIsTooltipVisible(!isTooltipVisible);
};
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: MouseEvent | 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 as EventListener, true);
document.addEventListener('mousedown', handleClickOutside as EventListener, true);
return () => {
document.removeEventListener('touchstart', handleClickOutside as EventListener, true);
document.removeEventListener('mousedown', handleClickOutside as EventListener, true);
};
}, [handleClickOutside]);
return (
<>
<div
ref={setTriggerRef}
className="truncate"
onClick={handleClick}
onTouchStart={handleTouch}
>
{label}
</div>
<Transition
show={isTooltipVisible || visible}
enter="transition-opacity duration-200 ease-out"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-200 ease-in"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
ref={setTooltipRef}
{...getTooltipProps({
className: classNames(
maxWidth,
"z-10 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 ? (
<div className="flex justify-between items-center p-2 border-b border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-800 rounded-t-md">
{title}
</div>
) : null}
<div
className={classNames(
title ? "" : "rounded-t-md",
"whitespace-normal break-words py-1 px-2 rounded-b-md bg-white dark:bg-gray-900"
)}
>
{children}
</div>
</div>
</Transition>
</>
);
};