fix(web): tooltips (#1154)

* fix broken tooltips, replace react-tooltip with react-popper-tooltip

* make tooltips use ExternalLink component

* fix import

* get rid of unused import

* fix(tooltip): set delayHide

* removed unecessary comment

* fix tooltip on mobile

* stop tooltip label propagation (mainly for mobile devices)

* added onClick convenience prop for label component wrapper (since onClick isn't propagated down)

---------

Co-authored-by: soup <soup@r4tio.dev>
This commit is contained in:
stacksmash76 2023-10-01 13:16:05 +00:00 committed by GitHub
parent 98df0c9040
commit 3e3454724b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1171 additions and 396 deletions

View file

@ -0,0 +1,20 @@
type ExternalLinkProps = {
href: string;
className?: string;
children?: React.ReactNode;
};
export const ExternalLink = ({ href, className, children }: ExternalLinkProps) => (
<a
rel="noopener noreferrer"
target="_blank"
href={href}
className={className}
>
{children}
</a>
);
export const DocsLink = ({ href }: { href: string; }) => (
<ExternalLink href={href} className="text-blue-400 visited:text-blue-400">{href}</ExternalLink>
);

View file

@ -6,6 +6,7 @@
import StackTracey from "stacktracey";
import type { FallbackProps } from "react-error-boundary";
import { ArrowPathIcon } from "@heroicons/react/24/solid";
import { ExternalLink } from "@components/ExternalLink";
export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
const stack = new StackTracey(error);
@ -35,23 +36,19 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
<h3 className="text-xl leading-6 text-gray-700 dark:text-gray-400 mb-4">
Please consider reporting this error to our
{" "}
<a
rel="noopener noreferrer"
target="_blank"
<ExternalLink
href="https://github.com/autobrr/autobrr"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-sky-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
>
GitHub page
</a>
</ExternalLink>
{" or to "}
<a
rel="noopener noreferrer"
target="_blank"
<ExternalLink
href="https://discord.gg/WQ2eUycxyT"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
>
our official Discord channel
</a>
</ExternalLink>
.
</h3>
<div

View file

@ -5,6 +5,7 @@
import { Link } from "react-router-dom";
import { ReactComponent as Logo } from "@app/logo.svg";
import { ExternalLink } from "@components/ExternalLink";
export const NotFound = () => {
return (
@ -21,23 +22,19 @@ export const NotFound = () => {
<h3 className="text-xl text-center text-gray-700 dark:text-gray-400 mb-1 px-2">
feel free to report this to our
{" "}
<a
rel="noopener noreferrer"
target="_blank"
<ExternalLink
href="https://github.com/autobrr/autobrr"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-sky-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
>
GitHub page
</a>
</ExternalLink>
{" or to "}
<a
rel="noopener noreferrer"
target="_blank"
<ExternalLink
href="https://discord.gg/WQ2eUycxyT"
className="text-gray-700 dark:text-gray-200 underline font-semibold underline-offset-2 decoration-purple-500 hover:decoration-2 hover:text-black hover:dark:text-gray-100"
>
our official Discord channel
</a>
</ExternalLink>
.
</h3>
<h3 className="text-xl text-center leading-6 text-gray-700 dark:text-gray-400 mb-8 px-2">

View file

@ -4,18 +4,18 @@
*/
import * as React from "react";
import { toast } from "react-hot-toast";
import { formatDistanceToNowStrict } from "date-fns";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid";
import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
import { classNames, simplifyDate } from "@utils";
import { Tooltip } from "@components/tooltips/Tooltip";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { APIClient } from "@api/APIClient";
import { classNames, simplifyDate } from "@utils";
import { filterKeys } from "@screens/filters/List";
import { toast } from "react-hot-toast";
import Toast from "@components/notifications/Toast";
import { RingResizeSpinner } from "@components/Icons";
import { Tooltip } from "@components/tooltips/Tooltip";
interface CellProps {
value: string;
@ -35,6 +35,7 @@ export const IndexerCell = ({ value }: CellProps) => (
)}
>
<Tooltip
requiresClick
label={value}
maxWidth="max-w-[90vw]"
>
@ -53,6 +54,7 @@ export const TitleCell = ({ value }: CellProps) => (
)}
>
<Tooltip
requiresClick
label={value}
maxWidth="max-w-[90vw]"
>
@ -221,6 +223,7 @@ export const ReleaseStatusCell = ({ value }: ReleaseStatusCellProps) => (
)}
>
<Tooltip
requiresClick
label={StatusCellMap[v.status].icon}
title={StatusCellMap[v.status].textFormatter(v)}
>

View file

@ -15,6 +15,7 @@ import Toast from "@components/notifications/Toast";
import { LeftNav } from "./LeftNav";
import { RightNav } from "./RightNav";
import { MobileNav } from "./MobileNav";
import { ExternalLink } from "@components/ExternalLink";
export const Header = () => {
const { data: config } = useQuery({
@ -77,13 +78,13 @@ export const Header = () => {
</div>
{data?.html_url && (
<a href={data.html_url} target="_blank" rel="noopener noreferrer">
<ExternalLink href={data.html_url}>
<div className="flex mt-4 py-2 bg-blue-500 rounded justify-center">
<MegaphoneIcon className="h-6 w-6 text-blue-100" />
<span className="text-blue-100 font-medium mx-3">New update available!</span>
<span className="inline-flex items-center rounded-md bg-blue-100 px-2.5 py-0.5 text-sm font-medium text-blue-800">{data?.name}</span>
</div>
</a>
</ExternalLink>
)}
</div>

View file

@ -10,6 +10,7 @@ import { classNames } from "@utils";
import { ReactComponent as Logo } from "@app/logo.svg";
import { NAV_ROUTES } from "./_shared";
import { ExternalLink } from "@components/ExternalLink";
export const LeftNav = () => (
<div className="flex items-center">
@ -38,9 +39,7 @@ export const LeftNav = () => (
{item.name}
</NavLink>
))}
<a
rel="noopener noreferrer"
target="_blank"
<ExternalLink
href="https://autobrr.com"
className={classNames(
"text-gray-600 dark:text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-2xl text-sm font-medium",
@ -52,7 +51,7 @@ export const LeftNav = () => (
className="inline ml-1 h-5 w-5"
aria-hidden="true"
/>
</a>
</ExternalLink>
</div>
</div>
</div>

View file

@ -5,7 +5,7 @@
import { Field, FieldProps } from "formik";
import { classNames } from "@utils";
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface ErrorFieldProps {
name: string;
@ -63,10 +63,9 @@ const CheckboxField = ({
<div className="ml-3 text-sm">
<label htmlFor={name} className="flex mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex">
{label}
{tooltip && (
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
<p className="text-gray-500">{sublabel}</p>
@ -74,4 +73,4 @@ const CheckboxField = ({
</div>
);
export { ErrorField, RequiredField, CheckboxField };
export { ErrorField, RequiredField, CheckboxField };

View file

@ -9,7 +9,7 @@ import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/
import TextareaAutosize from "react-textarea-autosize";
import { useToggle } from "@hooks/hooks";
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
import { DocsTooltip } from "@components/tooltips/DocsTooltip";
import { classNames } from "@utils";
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
@ -46,10 +46,9 @@ export const TextField = ({
{label && (
<label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex">
{label}
{tooltip && (
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
)}
@ -185,8 +184,9 @@ export const RegexField = ({
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
>
<div className="flex">
{label}
<span className="z-10">{tooltip && <CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>}</span>
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
)}
@ -324,9 +324,10 @@ export const RegexTextAreaField = ({
htmlFor={name}
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
>
<div className="flex">
{label}
<span className="z-10">{tooltip && <CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>}</span>
<div className="flex z-10">
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
)}
@ -412,10 +413,9 @@ export const TextArea = ({
{label && (
<label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex">
{label}
{tooltip && (
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
)}
@ -484,10 +484,9 @@ export const TextAreaAutoResize = ({
{label && (
<label htmlFor={name} className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex">
{label}
{tooltip && (
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
)}
@ -630,8 +629,9 @@ export const NumberField = ({
className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"
>
<div className="flex">
{label}
{tooltip && <CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>

View file

@ -12,7 +12,7 @@ import { Switch } from "@headlessui/react";
import { ErrorField, RequiredField } from "./common";
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import { SelectFieldProps } from "./select";
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface TextFieldWideProps {
name: string;
@ -43,7 +43,9 @@ export const TextFieldWide = ({
<div>
<label htmlFor={name} className="flex text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
<div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
<RequiredField required={required} />
</div>
</label>
@ -108,7 +110,9 @@ export const PasswordFieldWide = ({
<div>
<label htmlFor={name} className="flex text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
<div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
<RequiredField required={required} />
</div>
</label>
@ -172,7 +176,9 @@ export const NumberFieldWide = ({
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
>
<div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
<RequiredField required={required} />
</div>
</label>
@ -232,10 +238,11 @@ export const SwitchGroupWide = ({
<ul className="px-4 divide-y divide-gray-200 dark:divide-gray-700">
<Switch.Group as="li" className="py-4 flex items-center justify-between">
<div className="flex flex-col">
<Switch.Label as="div" className="text-sm font-medium text-gray-900 dark:text-white"
passive>
<Switch.Label as="div" passive className="text-sm font-medium text-gray-900 dark:text-white">
<div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</Switch.Label>
{description && (
@ -396,8 +403,9 @@ export const SelectFieldWide = ({
className="flex text-sm font-medium text-gray-900 dark:text-white"
>
<div className="flex">
{label}
{tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
</div>

View file

@ -11,7 +11,7 @@ import { MultiSelect as RMSC } from "react-multi-select-component";
import { classNames, COL_WIDTHS } from "@utils";
import { SettingsContext } from "@utils/Context";
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
import { DocsTooltip } from "@components/tooltips/DocsTooltip";
export interface MultiSelectOption {
value: string | number;
@ -56,10 +56,9 @@ export const MultiSelect = ({
<label
htmlFor={label} className="flex mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200">
<div className="flex">
{label}
{tooltip && (
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
@ -297,10 +296,9 @@ export const Select = ({
<>
<Listbox.Label className="flex float-left mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
<div className="flex">
{label}
{tooltip && (
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</Listbox.Label>
<div className="mt-2 relative">

View file

@ -8,7 +8,7 @@ import { Field } from "formik";
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import { OptionBasicTyped } from "@domain/constants";
import CreatableSelect from "react-select/creatable";
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
import { DocsTooltip } from "@components/tooltips/DocsTooltip";
interface SelectFieldProps<T> {
name: string;
@ -29,7 +29,9 @@ export function SelectFieldCreatable<T>({ name, label, help, placeholder, toolti
className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2"
>
<div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
</div>
@ -200,7 +202,9 @@ export function SelectFieldBasic<T>({ name, label, help, placeholder, tooltip, d
className="block text-sm font-medium text-gray-900 dark:text-white sm:pt-2"
>
<div className="flex">
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</label>
</div>

View file

@ -9,7 +9,7 @@ import { Field } from "formik";
import { Switch as HeadlessSwitch } from "@headlessui/react";
import { classNames } from "@utils";
import { CustomTooltip } from "@components/tooltips/CustomTooltip";
import { DocsTooltip } from "@components/tooltips/DocsTooltip";
type SwitchProps<V = unknown> = {
label?: string
@ -88,13 +88,18 @@ const SwitchGroup = ({
}: SwitchGroupProps) => (
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
{label && <div className="flex flex-col">
<HeadlessSwitch.Label as={heading ? "h2" : "span"} className={classNames("flex float-left cursor-default mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide", heading ? "text-lg" : "text-sm")}
passive>
<HeadlessSwitch.Label
passive
as={heading ? "h2" : "span"}
className={classNames(
"flex float-left cursor-default mb-2 text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide",
heading ? "text-lg" : "text-sm"
)}
>
<div className="flex">
{label}
{tooltip && (
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
)}
{tooltip ? (
<DocsTooltip label={label}>{tooltip}</DocsTooltip>
) : label}
</div>
</HeadlessSwitch.Label>
{description && (

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
.react-tooltip code {
background-color: rgb(63, 71, 94);
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px;
}
.react-tooltip a:hover {
text-decoration-line: underline;
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { PlacesType, Tooltip } from "react-tooltip";
import "./CustomTooltip.css";
interface CustomTooltipProps {
anchorId: string;
children?: React.ReactNode;
clickable?: boolean;
place?: PlacesType;
}
export const CustomTooltip = ({
anchorId,
children,
clickable = true,
place = "top"
}: CustomTooltipProps) => {
const id = `${anchorId}-tooltip`;
return (
<div className="flex items-center">
<svg id={id} className="ml-1 w-4 h-4 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10"/></svg>
<Tooltip
style={{ maxWidth: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff" }}
delayShow={100}
delayHide={150}
place={place}
anchorSelect={id}
data-html={true}
clickable={clickable}
>
{children}
</Tooltip>
</div>
);
};

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { Tooltip } from "./Tooltip";
interface DocsTooltipProps {
label?: React.ReactNode;
children?: React.ReactNode;
}
export const DocsTooltip = ({ label, children }: DocsTooltipProps) => (
<Tooltip
label={
<div className="flex flex-row items-center">
{label ?? null}
<svg className="ml-1 w-4 h-4 text-gray-500 dark:text-gray-400 fill-current" viewBox="0 0 72 72"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2m5 49.75H27v-24h10v24m-5-29.5a5 5 0 1 1 0-10a5 5 0 0 1 0 10" /></svg>
</div>
}
>
{children}
</Tooltip>
);

View file

@ -3,22 +3,30 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import * as React from "react";
import type { ReactNode } from "react";
import { Transition } from "@headlessui/react";
import { usePopperTooltip } from "react-popper-tooltip";
import { classNames } from "@utils";
interface TooltipProps {
label: ReactNode;
onLabelClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
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,
onLabelClick,
title,
children,
requiresClick,
maxWidth = "max-w-sm"
}: TooltipProps) => {
const {
@ -28,22 +36,46 @@ export const Tooltip = ({
setTriggerRef,
visible
} = usePopperTooltip({
trigger: ["click"],
interactive: false
trigger: requiresClick ? ["click"] : ["click", "hover"],
interactive: !requiresClick,
delayHide: 200
});
if (!children || Array.isArray(children) && !children.length) {
return null;
}
return (
<>
<div ref={setTriggerRef} className="truncate">
<div
ref={setTriggerRef}
className="truncate"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
onLabelClick?.(e);
}}
>
{label}
</div>
{visible && (
<Transition
show={visible}
className="z-10"
enter="transition duration-200 ease-out"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition duration-200 ease-in"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
ref={setTooltipRef}
{...getTooltipProps({
className: classNames(
maxWidth,
"rounded-md border border-gray-300 text-black text-xs 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"
)
})}
>
@ -55,13 +87,13 @@ export const Tooltip = ({
<div
className={classNames(
title ? "" : "rounded-t-md",
"py-1 px-2 rounded-b-md bg-white dark:bg-gray-900"
"whitespace-normal break-words py-1 px-2 rounded-b-md bg-white dark:bg-gray-900"
)}
>
{children}
</div>
</div>
)}
</Transition>
</>
);
};
};