mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 16:29:12 +00:00

* 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
154 lines
4.6 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
};
|