mirror of
https://github.com/idanoo/autobrr
synced 2025-07-25 17:59:14 +00:00
feat(web): Add helper tooltips for inputs and link them to docs (#663)
* Fixed button border in settings/download-clients Button border for "Add new" is now uniform with other "Add new"-buttons * enhancement(docs) add helper tooltips - Add helper tooltips for inputs and link them to docs #161 * fix build error * tooltips: changed positition * hide tooltip below 640 width * made tooltips better now attaching to TextField names * Added icon variation for MultiSelect and CheckboxField * cleaned up old code * refactor Co-authored-by: ze0s <zze0s@users.noreply.github.com> * added tooltips for DownloadClientForms * added tooltips for indexerforms * div for passwordfieldwide * tooltips for details.tsx * added tooltips to actions.tsx * added tooltips to indexerforms.tsx * replaced info icon with a more rudimentary one * linting, removed duplicate tailwind display properties * remove margin for flex centering * fixed tooltip alignment on all fields * add tooltip to PwFieldWide in indexer edit window * refactor: simplify tooltips * refactor: scope tooltip css * refactor: tooltip default clickable --------- Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com> Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com>
This commit is contained in:
parent
a50332394c
commit
3fdd7cf5e4
15 changed files with 256 additions and 90 deletions
|
@ -1,5 +1,6 @@
|
|||
import { Field, FieldProps } from "formik";
|
||||
import { classNames } from "../../utils";
|
||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||
|
||||
interface ErrorFieldProps {
|
||||
name: string;
|
||||
|
@ -16,17 +17,29 @@ const ErrorField = ({ name, classNames }: ErrorFieldProps) => (
|
|||
</div>
|
||||
);
|
||||
|
||||
interface RequiredFieldProps {
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
const RequiredField = ({ required }: RequiredFieldProps) => (
|
||||
<>
|
||||
{required && <span className="ml-1 text-red-500">*</span>}
|
||||
</>
|
||||
);
|
||||
|
||||
interface CheckboxFieldProps {
|
||||
name: string;
|
||||
label: string;
|
||||
sublabel?: string;
|
||||
disabled?: boolean;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
const CheckboxField = ({
|
||||
name,
|
||||
label,
|
||||
sublabel,
|
||||
tooltip,
|
||||
disabled
|
||||
}: CheckboxFieldProps) => (
|
||||
<div className="relative flex items-start">
|
||||
|
@ -43,12 +56,17 @@ const CheckboxField = ({
|
|||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
<label htmlFor={name} className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{label}
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
<p className="text-gray-500">{sublabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { ErrorField, CheckboxField };
|
||||
export { ErrorField, RequiredField, CheckboxField };
|
|
@ -2,6 +2,7 @@ import { Field, FieldProps } from "formik";
|
|||
import { classNames } from "../../utils";
|
||||
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||
|
||||
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
|
||||
|
@ -14,6 +15,7 @@ interface TextFieldProps {
|
|||
autoComplete?: string;
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
export const TextField = ({
|
||||
|
@ -24,6 +26,7 @@ export const TextField = ({
|
|||
columns,
|
||||
autoComplete,
|
||||
hidden,
|
||||
tooltip,
|
||||
disabled
|
||||
}: TextFieldProps) => (
|
||||
<div
|
||||
|
@ -33,8 +36,13 @@ export const TextField = ({
|
|||
)}
|
||||
>
|
||||
{label && (
|
||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
)}
|
||||
<Field name={name}>
|
||||
|
@ -45,7 +53,7 @@ export const TextField = ({
|
|||
<div>
|
||||
<input
|
||||
{...field}
|
||||
id={name}
|
||||
name={name}
|
||||
type="text"
|
||||
defaultValue={defaultValue}
|
||||
autoComplete={autoComplete}
|
||||
|
@ -207,6 +215,7 @@ interface NumberFieldProps {
|
|||
required?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
export const NumberField = ({
|
||||
|
@ -216,12 +225,18 @@ export const NumberField = ({
|
|||
step,
|
||||
min,
|
||||
max,
|
||||
tooltip,
|
||||
disabled,
|
||||
required
|
||||
}: NumberFieldProps) => (
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<Field name={name} type="number">
|
||||
|
|
|
@ -4,9 +4,10 @@ import { classNames } from "../../utils";
|
|||
import { useToggle } from "../../hooks/hooks";
|
||||
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
||||
import { Switch } from "@headlessui/react";
|
||||
import { ErrorField } from "./common";
|
||||
import { ErrorField, RequiredField } from "./common";
|
||||
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
|
||||
import { SelectFieldProps } from "./select";
|
||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||
|
||||
interface TextFieldWideProps {
|
||||
name: string;
|
||||
|
@ -16,6 +17,7 @@ interface TextFieldWideProps {
|
|||
defaultValue?: string;
|
||||
required?: boolean;
|
||||
hidden?: boolean;
|
||||
tooltip?: JSX.Element;
|
||||
validate?: FieldValidator;
|
||||
}
|
||||
|
||||
|
@ -26,13 +28,17 @@ export const TextFieldWide = ({
|
|||
placeholder,
|
||||
defaultValue,
|
||||
required,
|
||||
tooltip,
|
||||
hidden,
|
||||
validate
|
||||
}: TextFieldWideProps) => (
|
||||
<div hidden={hidden} className="space-y-1 p-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<div>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
|
||||
{label} {required && <span className="text-gray-500">*</span>}
|
||||
<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>)}
|
||||
<RequiredField required={required} />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
|
@ -71,6 +77,7 @@ interface PasswordFieldWideProps {
|
|||
help?: string;
|
||||
required?: boolean;
|
||||
defaultVisible?: boolean;
|
||||
tooltip?: JSX.Element;
|
||||
validate?: FieldValidator;
|
||||
}
|
||||
|
||||
|
@ -82,6 +89,7 @@ export const PasswordFieldWide = ({
|
|||
help,
|
||||
required,
|
||||
defaultVisible,
|
||||
tooltip,
|
||||
validate
|
||||
}: PasswordFieldWideProps) => {
|
||||
const [isVisible, toggleVisibility] = useToggle(defaultVisible);
|
||||
|
@ -89,8 +97,11 @@ export const PasswordFieldWide = ({
|
|||
return (
|
||||
<div className="space-y-1 p-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<div>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
|
||||
{label} {required && <span className="text-gray-500">*</span>}
|
||||
<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>)}
|
||||
<RequiredField required={required} />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
|
@ -132,6 +143,7 @@ interface NumberFieldWideProps {
|
|||
placeholder?: string;
|
||||
defaultValue?: number;
|
||||
required?: boolean;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
export const NumberFieldWide = ({
|
||||
|
@ -140,6 +152,7 @@ export const NumberFieldWide = ({
|
|||
placeholder,
|
||||
help,
|
||||
defaultValue,
|
||||
tooltip,
|
||||
required
|
||||
}: NumberFieldWideProps) => (
|
||||
<div className="px-4 space-y-1 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
|
||||
|
@ -148,7 +161,10 @@ export const NumberFieldWide = ({
|
|||
htmlFor={name}
|
||||
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
|
||||
>
|
||||
{label} {required && <span className="text-gray-500">*</span>}
|
||||
<div className="flex">
|
||||
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
|
||||
<RequiredField required={required} />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
|
@ -187,12 +203,14 @@ interface SwitchGroupWideProps {
|
|||
description?: string;
|
||||
defaultValue?: boolean;
|
||||
className?: string;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
export const SwitchGroupWide = ({
|
||||
name,
|
||||
label,
|
||||
description,
|
||||
tooltip,
|
||||
defaultValue
|
||||
}: SwitchGroupWideProps) => (
|
||||
<ul className="mt-2 px-4 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
|
@ -200,7 +218,9 @@ export const SwitchGroupWide = ({
|
|||
<div className="flex flex-col">
|
||||
<Switch.Label as="p" className="text-sm font-medium text-gray-900 dark:text-white"
|
||||
passive>
|
||||
{label}
|
||||
<div className="flex">
|
||||
{label} {tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
|
||||
</div>
|
||||
</Switch.Label>
|
||||
{description && (
|
||||
<Switch.Description className="text-sm text-gray-500 dark:text-gray-700">
|
||||
|
@ -350,15 +370,19 @@ export const SelectFieldWide = ({
|
|||
name,
|
||||
label,
|
||||
optionDefaultText,
|
||||
tooltip,
|
||||
options
|
||||
}: SelectFieldProps) => (
|
||||
<div className="flex items-center justify-between space-y-1 px-4 py-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="block text-sm font-medium text-gray-900 dark:text-white"
|
||||
className="flex text-sm font-medium text-gray-900 dark:text-white"
|
||||
>
|
||||
{label}
|
||||
<div className="flex">
|
||||
{label}
|
||||
{tooltip && (<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Fragment } from "react";
|
||||
import React, { Fragment } from "react";
|
||||
import { Field, FieldProps } from "formik";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/solid";
|
||||
|
@ -6,6 +6,7 @@ import { MultiSelect as RMSC } from "react-multi-select-component";
|
|||
|
||||
import { classNames, COL_WIDTHS } from "../../utils";
|
||||
import { SettingsContext } from "../../utils/Context";
|
||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||
|
||||
export interface MultiSelectOption {
|
||||
value: string | number;
|
||||
|
@ -21,6 +22,7 @@ interface MultiSelectProps {
|
|||
columns?: COL_WIDTHS;
|
||||
creatable?: boolean;
|
||||
disabled?: boolean;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
export const MultiSelect = ({
|
||||
|
@ -29,6 +31,7 @@ export const MultiSelect = ({
|
|||
options,
|
||||
columns,
|
||||
creatable,
|
||||
tooltip,
|
||||
disabled
|
||||
}: MultiSelectProps) => {
|
||||
const settingsContext = SettingsContext.useValue();
|
||||
|
@ -46,10 +49,13 @@ export const MultiSelect = ({
|
|||
)}
|
||||
>
|
||||
<label
|
||||
className="block mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200"
|
||||
htmlFor={label}
|
||||
>
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<Field name={name} type="select" multiple={true}>
|
||||
|
@ -155,7 +161,7 @@ export function DownloadClientSelect({
|
|||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Label className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
||||
Client
|
||||
Client
|
||||
</Listbox.Label>
|
||||
<div className="mt-2 relative">
|
||||
<Listbox.Button className="bg-white dark:bg-gray-800 relative w-full border border-gray-300 dark:border-gray-700 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500 dark:text-gray-200 sm:text-sm">
|
||||
|
@ -244,11 +250,13 @@ export interface SelectFieldProps {
|
|||
label: string;
|
||||
optionDefaultText: string;
|
||||
options: SelectFieldOption[];
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
export const Select = ({
|
||||
name,
|
||||
label,
|
||||
tooltip,
|
||||
optionDefaultText,
|
||||
options
|
||||
}: SelectFieldProps) => {
|
||||
|
@ -265,8 +273,13 @@ export const Select = ({
|
|||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Label className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
||||
{label}
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
</Listbox.Label>
|
||||
<div className="mt-2 relative">
|
||||
<Listbox.Button className="bg-white dark:bg-gray-800 relative w-full border border-gray-300 dark:border-gray-700 rounded-md shadow-sm pl-3 pr-10 py-2.5 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500 dark:text-gray-200 sm:text-sm">
|
||||
|
|
|
@ -3,6 +3,7 @@ import type { FieldInputProps, FieldMetaProps, FieldProps, FormikProps, FormikVa
|
|||
import { Field } from "formik";
|
||||
import { Switch as HeadlessSwitch } from "@headlessui/react";
|
||||
import { classNames } from "../../utils";
|
||||
import { CustomTooltip } from "../tooltips/CustomTooltip";
|
||||
|
||||
type SwitchProps<V = unknown> = {
|
||||
label?: string
|
||||
|
@ -69,19 +70,26 @@ interface SwitchGroupProps {
|
|||
description?: string;
|
||||
className?: string;
|
||||
heading?: boolean;
|
||||
tooltip?: JSX.Element;
|
||||
}
|
||||
|
||||
const SwitchGroup = ({
|
||||
name,
|
||||
label,
|
||||
description,
|
||||
tooltip,
|
||||
heading
|
||||
}: SwitchGroupProps) => (
|
||||
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
|
||||
{label && <div className="flex flex-col">
|
||||
<HeadlessSwitch.Label as={heading ? "h2" : "p"} className={classNames("font-medium text-gray-900 dark:text-gray-100", heading ? "text-lg" : "text-sm")}
|
||||
<HeadlessSwitch.Label as={heading ? "h2" : "p"} 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>
|
||||
{label}
|
||||
<div className="flex">
|
||||
{label}
|
||||
{tooltip && (
|
||||
<CustomTooltip anchorId={name}>{tooltip}</CustomTooltip>
|
||||
)}
|
||||
</div>
|
||||
</HeadlessSwitch.Label>
|
||||
{description && (
|
||||
<HeadlessSwitch.Description className="text-sm mt-1 text-gray-500 dark:text-gray-400">
|
||||
|
|
11
web/src/components/tooltips/CustomTooltip.css
Normal file
11
web/src/components/tooltips/CustomTooltip.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.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;
|
||||
}
|
27
web/src/components/tooltips/CustomTooltip.tsx
Normal file
27
web/src/components/tooltips/CustomTooltip.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
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" 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" fill="currentcolor"/></svg>
|
||||
<Tooltip style= {{ maxWidth: "350px", fontSize: "12px", textTransform: "none", fontWeight: "normal", borderRadius: "0.375rem", backgroundColor: "#34343A", color: "#fff", opacity: "1" }} delayShow={100} delayHide={150} place={place} anchorId={id} data-html={true} clickable={clickable}>
|
||||
{children}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue