47 lines
2.2 KiB
TypeScript
47 lines
2.2 KiB
TypeScript
/**
|
||
* 画布等工具栏使用的图标按钮。统一按钮尺寸、图标与可访问名称,并按反馈Provider决定使用浮层提示还是原生title。
|
||
* 按钮的业务动作由onClick等原生属性传入;这里不维护工具选择状态。
|
||
*/
|
||
"use client";
|
||
|
||
import { FeedbackContext } from "./app-feedback";
|
||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||
import { useContext, forwardRef, type ButtonHTMLAttributes } from "react";
|
||
import type { LucideIcon } from "lucide-react";
|
||
import styles from "./icon-button.module.css";
|
||
|
||
/** icon提供Lucide图标实现,label提供默认可访问名称与提示;不接受任意children替换内部图标。 */
|
||
export type IconButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
|
||
icon: LucideIcon;
|
||
label: string;
|
||
};
|
||
|
||
/** 将ref交给真实button;type默认为button,避免放进表单时意外提交。
|
||
* 调用方仍可传aria-pressed、disabled等状态,组件不会自行判断命令是否可用。 */
|
||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||
function IconButton({ icon: Icon, label, title = label, className = "", type = "button", ...props }, ref) {
|
||
// Provider存在时由Radix显示提示,移除原生title以免出现两层提示;图标自身对读屏隐藏。
|
||
// 后置 ...props 可以覆盖 aria-label 等同名属性;label 是默认名称,最终 DOM 语义由调用者共同负责。
|
||
const feedback = useContext(FeedbackContext);
|
||
const button = (
|
||
<button
|
||
ref={ref}
|
||
type={type}
|
||
aria-label={label}
|
||
title={feedback ? undefined : title}
|
||
className={`${styles.button} ${className}`}
|
||
{...props}
|
||
>
|
||
<Icon size={16} strokeWidth={1.6} aria-hidden="true" />
|
||
</button>
|
||
);
|
||
// 没有全局提示环境就返回原生按钮;空title也不创建Tooltip。提示通过Portal避免被工具栏裁切。
|
||
if (!feedback || !title) return button;
|
||
return <Tooltip.Root><Tooltip.Trigger asChild>{button}</Tooltip.Trigger><Tooltip.Portal>
|
||
<Tooltip.Content className={styles.tooltip} sideOffset={7} collisionPadding={8}>
|
||
{title}<Tooltip.Arrow className={styles.arrow} />
|
||
</Tooltip.Content>
|
||
</Tooltip.Portal></Tooltip.Root>;
|
||
},
|
||
);
|