mirror of https://github.com/usememos/memos
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
8.1 KiB
TypeScript
284 lines
8.1 KiB
TypeScript
import { ReactNode } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Button, IconButton, Tooltip } from "@mui/joy";
|
|
import { generateDialog } from "../Dialog";
|
|
import Icon from "../Icon";
|
|
|
|
const openUrl = (url?: string) => {
|
|
window.open(url, "_blank");
|
|
};
|
|
|
|
/** Options for {@link HelpButton} */
|
|
interface HelpProps {
|
|
/**
|
|
* Plain text to show in the dialog.
|
|
*
|
|
* If the text contains "\n", it will be split to multiple paragraphs.
|
|
*/
|
|
text?: string;
|
|
/**
|
|
* The title of the dialog.
|
|
*
|
|
* If not provided, the title will be set according to the `icon` prop.
|
|
*/
|
|
title?: string;
|
|
/**
|
|
* External documentation URL.
|
|
*
|
|
* If provided, this will be shown as a link button in the bottom of the dialog.
|
|
*
|
|
* If provided alone, the button will just open the URL in a new tab.
|
|
*
|
|
* @param {string} url - External URL to the documentation.
|
|
*/
|
|
url?: string;
|
|
/**
|
|
* The tooltip of the button.
|
|
*/
|
|
hint?: string | "none";
|
|
/**
|
|
* The placement of the hovering hint.
|
|
* @defaultValue "top"
|
|
*/
|
|
hintPlacement?: "top" | "bottom" | "left" | "right";
|
|
/**
|
|
* The icon to show in the button.
|
|
*
|
|
* Also used to infer `title` and `hint`, if they are not provided.
|
|
*
|
|
* @defaultValue Icon.HelpCircle
|
|
* @see {@link Icon.LucideIcon}
|
|
*/
|
|
icon?: Icon.LucideIcon | "link" | "info" | "help" | "alert" | "warn";
|
|
/**
|
|
* The className for the button.
|
|
* @defaultValue `!-mt-2` (aligns the button vertically with nearby text)
|
|
*/
|
|
className?: string;
|
|
/**
|
|
* The color of the button.
|
|
* @defaultValue "neutral"
|
|
*/
|
|
color?: "primary" | "neutral" | "danger" | "info" | "success" | "warning";
|
|
/**
|
|
* The variant of the button.
|
|
* @defaultValue "plain"
|
|
*/
|
|
variant?: "plain" | "outlined" | "soft" | "solid";
|
|
/**
|
|
* The size of the button.
|
|
* @defaultValue "md"
|
|
*/
|
|
size?: "sm" | "md" | "lg";
|
|
/**
|
|
* `ReactNode` HTML content to show in the dialog.
|
|
*
|
|
* If provided, will be shown before `text`.
|
|
*
|
|
* You'll probably want to use `text` instead.
|
|
*/
|
|
children?: ReactNode | undefined;
|
|
}
|
|
|
|
interface HelpDialogProps extends HelpProps, DialogProps {}
|
|
|
|
const HelpfulDialog: React.FC<HelpDialogProps> = (props: HelpDialogProps) => {
|
|
const { t } = useTranslation();
|
|
const { children, destroy, icon } = props;
|
|
const LucideIcon = icon as Icon.LucideIcon;
|
|
const handleCloseBtnClick = () => {
|
|
destroy();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="dialog-header-container">
|
|
<LucideIcon size="24" />
|
|
<p className="title-text text-left">{props.title}</p>
|
|
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
|
<Icon.X />
|
|
</button>
|
|
</div>
|
|
<div className="dialog-content-container max-w-sm">
|
|
{children}
|
|
{props.text
|
|
? props.text.split(/\n|\\n/).map((text) => {
|
|
return (
|
|
<p key={text} className="mt-2 break-words text-justify">
|
|
{text}
|
|
</p>
|
|
);
|
|
})
|
|
: null}
|
|
<div className="mt-2 w-full flex flex-row justify-end space-x-2">
|
|
{props.url ? (
|
|
<Button className="btn-normal" variant="outlined" color={props.color} onClick={() => openUrl(props.url)}>
|
|
{t("common.learn-more")}
|
|
<Icon.ExternalLink className="ml-1 w-4 h-4 opacity-80" />
|
|
</Button>
|
|
) : null}
|
|
<Button className="btn-normal" variant="outlined" color={props.color} onClick={handleCloseBtnClick}>
|
|
{t("common.close")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
function showHelpDialog(props: HelpProps) {
|
|
generateDialog(
|
|
{
|
|
className: "help-dialog",
|
|
dialogName: "help-dialog",
|
|
clickSpaceDestroy: true,
|
|
},
|
|
HelpfulDialog,
|
|
props
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Show a helpful `IconButton` that behaves differently depending on the props.
|
|
*
|
|
* The main purpose of this component is to avoid UI clutter.
|
|
*
|
|
* Use the property `icon` to set the icon and infer the title and hint automatically.
|
|
*
|
|
* Use cases:
|
|
* - Button with just a hover hint
|
|
* - Button with a hover hint and link
|
|
* - Button with a hover hint that opens a dialog with text and a link.
|
|
*
|
|
* @example
|
|
* <Helpful hint="Hint" />
|
|
* <Helpful hint="This is a hint with a link" url="https://usememos.com/" />
|
|
* <Helpful icon="warn" text={t("i18n.key.long-dialog-text")} url="https://usememos.com/" />
|
|
* <Helpful />
|
|
*
|
|
* <div className="flex flex-row">
|
|
* <span className="ml-2">Sample alignment</span>
|
|
* <Helpful hint="Button with hint" />
|
|
* </div>
|
|
|
|
* @param props.title - The title of the dialog. Defaults to "Learn more" i18n key.
|
|
* @param props.text - Plain text to show in the dialog. Line breaks are supported.
|
|
* @param props.url - External memos documentation URL.
|
|
* @param props.hint - The hint when hovering the button.
|
|
* @param props.hintPlacement - The placement of the hovering hint. Defaults to "top".
|
|
* @param props.icon - The icon to show in the button.
|
|
* @param props.className - The class name for the button.
|
|
* @param {HelpProps} props - See {@link HelpDialogProps} for all exposed props.
|
|
*/
|
|
const HelpButton = (props: HelpProps): JSX.Element => {
|
|
const { t } = useTranslation();
|
|
const color = props.color ?? "neutral";
|
|
const variant = props.variant ?? "plain";
|
|
const className = props.className ?? "!-mt-1";
|
|
const hintPlacement = props.hintPlacement ?? "top";
|
|
const iconButtonSize = "sm";
|
|
|
|
const dialogAvailable = props.text || props.children;
|
|
const clickActionAvailable = props.url || dialogAvailable;
|
|
const onlyUrlAvailable = props.url && !dialogAvailable;
|
|
|
|
let LucideIcon = (() => {
|
|
switch (props.icon) {
|
|
case "info":
|
|
return Icon.Info;
|
|
case "help":
|
|
return Icon.HelpCircle;
|
|
case "warn":
|
|
case "alert":
|
|
return Icon.AlertTriangle;
|
|
case "link":
|
|
return Icon.ExternalLink;
|
|
default:
|
|
return Icon.HelpCircle;
|
|
}
|
|
})() as Icon.LucideIcon;
|
|
|
|
const hint = (() => {
|
|
switch (props.hint) {
|
|
case undefined:
|
|
return t(
|
|
(() => {
|
|
if (!dialogAvailable) {
|
|
LucideIcon = Icon.ExternalLink;
|
|
}
|
|
switch (LucideIcon) {
|
|
case Icon.Info:
|
|
return "common.dialog.info";
|
|
case Icon.AlertTriangle:
|
|
return "common.dialog.warning";
|
|
case Icon.ExternalLink:
|
|
return "common.learn-more";
|
|
case Icon.HelpCircle:
|
|
default:
|
|
return "common.dialog.help";
|
|
}
|
|
})()
|
|
);
|
|
case "":
|
|
case "none":
|
|
case "false":
|
|
case "disabled":
|
|
return undefined;
|
|
default:
|
|
return props.hint;
|
|
}
|
|
})();
|
|
|
|
const sizePx = (() => {
|
|
switch (props.size) {
|
|
case "sm":
|
|
return 16;
|
|
case "lg":
|
|
return 48;
|
|
case "md":
|
|
default:
|
|
return 24;
|
|
}
|
|
})();
|
|
|
|
if (!dialogAvailable && !clickActionAvailable && !props.hint) {
|
|
return (
|
|
<IconButton className={className} color={color} variant={variant} size={iconButtonSize}>
|
|
<LucideIcon size={sizePx} />
|
|
</IconButton>
|
|
);
|
|
}
|
|
|
|
const wrapInTooltip = (element: JSX.Element) => {
|
|
if (!hint) {
|
|
return element;
|
|
}
|
|
return (
|
|
<Tooltip placement={hintPlacement} title={hint} color={color} variant={variant} size={props.size}>
|
|
{element}
|
|
</Tooltip>
|
|
);
|
|
};
|
|
|
|
if (clickActionAvailable) {
|
|
props = { ...props, title: props.title ?? hint, hint: hint, color: color, variant: variant, icon: LucideIcon };
|
|
const clickAction = () => {
|
|
dialogAvailable ? showHelpDialog(props) : openUrl(props.url);
|
|
};
|
|
LucideIcon = dialogAvailable || onlyUrlAvailable ? LucideIcon : Icon.ExternalLink;
|
|
return wrapInTooltip(
|
|
<IconButton className={className} color={color} variant={variant} size={iconButtonSize} onClick={clickAction}>
|
|
<LucideIcon size={sizePx} />
|
|
</IconButton>
|
|
);
|
|
}
|
|
|
|
return wrapInTooltip(
|
|
<IconButton className={className} color={color} variant={variant} size={iconButtonSize}>
|
|
<LucideIcon size={sizePx} />
|
|
</IconButton>
|
|
);
|
|
};
|
|
|
|
export default HelpButton;
|