Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 9x 184x 124x 5x 184x 170x 169x 184x 184x 184x 184x 184x 184x 184x | import * as React from "react";
import type { ReactNode } from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { getStatusHoverTextClass, getStatusTextClass } from "@/lib/status-semantics";
interface ToolbarIconButtonProps
extends Omit<React.ComponentPropsWithoutRef<typeof Button>, "children" | "size" | "variant"> {
readonly icon: ReactNode;
readonly label: string;
readonly destructive?: boolean;
readonly pressed?: boolean;
readonly status?: "default" | "success" | "muted" | "bright" | "error";
readonly animated?: boolean;
readonly animation?: "generating" | "waiting" | "evaluating" | "error";
readonly iconSize?: "default" | "lg";
}
type ToolbarIconStatus = NonNullable<ToolbarIconButtonProps["status"]>;
const STATUS_CLASS_NAMES: Record<ToolbarIconStatus, string | undefined> = {
success: `${getStatusTextClass("success")} ${getStatusHoverTextClass("success")}`,
muted: "text-muted-foreground hover:text-muted-foreground",
bright: "text-foreground hover:text-foreground",
error: `${getStatusTextClass("error")} ${getStatusHoverTextClass("error")}`,
default: undefined,
};
function getToolbarIconStatusClassName(
status: ToolbarIconButtonProps["status"],
pressed: boolean | undefined,
) {
if (status !== undefined) return STATUS_CLASS_NAMES[status];
if (pressed === undefined) return undefined;
return pressed
? "text-status-success hover:text-status-success"
: "text-muted-foreground hover:text-muted-foreground";
}
function getToolbarIconAnimationClassName(
animation: ToolbarIconButtonProps["animation"],
animated: boolean,
) {
if (animation !== undefined) return `toolbar-icon-button-animation-${animation}`;
if (animated) return "toolbar-icon-button-animated";
return undefined;
}
/** Shared icon-only action used by compact panel toolbars. */
export function ToolbarIconButton({
icon,
label,
destructive = false,
pressed,
status,
animated = false,
animation,
iconSize = "default",
className,
title,
"aria-label": ariaLabel,
"aria-pressed": ariaPressed,
...props
}: Readonly<ToolbarIconButtonProps>) {
const resolvedAriaPressed = ariaPressed ?? pressed;
const statusClassName = getToolbarIconStatusClassName(status, pressed);
const animationClassName = getToolbarIconAnimationClassName(animation, animated);
let renderedIcon = icon;
Eif (React.isValidElement(icon)) {
renderedIcon = React.cloneElement(icon, {
className: cn(
destructive && "text-red-500",
animationClassName,
(icon.props as { className?: string }).className,
),
});
}
return (
<Button
{...props}
variant="ghost"
size="icon"
className={cn(
"hover:border-transparent",
statusClassName,
iconSize === "lg" ? "[&_svg]:!h-5 [&_svg]:!w-5" : undefined,
className,
)}
aria-label={ariaLabel ?? label}
title={title ?? label}
aria-pressed={resolvedAriaPressed}
>
{renderedIcon}
</Button>
);
}
|