refactor(ui): update ActionButton icon placement and refine Link rendering
All checks were successful
Deploy Flowstate to VPS / deploy (push) Successful in 46s

This commit is contained in:
Chneemann 2026-10-09 10:09:51 +02:00
parent 587d54865f
commit d5bae440fd
No known key found for this signature in database

View file

@ -1,6 +1,6 @@
/** /**
* @file app/components/ui/buttons/ActionButton.tsx * @file app/components/ui/buttons/ActionButton.tsx
* @description Client/Server UI component rendering a styled link button with customizable variants and optional Lucide icons. * @description UI component rendering a styled link button or native button with customizable variants and optional Lucide icons.
*/ */
import Link from "next/link"; import Link from "next/link";
@ -12,22 +12,25 @@ import { LucideIcon } from "lucide-react";
* *
* @interface ActionButtonProps * @interface ActionButtonProps
* @extends {ButtonHTMLAttributes<HTMLButtonElement>} * @extends {ButtonHTMLAttributes<HTMLButtonElement>}
* @property {string} [href] - Optional navigation target URL. Renders a Next.js Link component if defined. *
* @property {"primary" | "secondary" | "danger"} [variant="primary"] - Visual style variant of the button. * @property {string} [href] Optional navigation target URL. Renders a Next.js Link component if defined.
* @property {ReactNode} children - Button label content or child nodes. * @property {"primary" | "secondary" | "danger"} [variant="primary"] Visual style variant of the button.
* @property {LucideIcon} [icon] - Optional Lucide icon component to display alongside the text label. * @property {ReactNode} children Button label content or child nodes.
* @property {LucideIcon} [icon] Optional Lucide icon component to display alongside the text label.
* @property {"start" | "end"} [iconPosition="end"] Explicitly sets the icon position (defaults to "end").
*/ */
interface ActionButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { interface ActionButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
href?: string; href?: string;
variant?: "primary" | "secondary" | "danger"; variant?: "primary" | "secondary" | "danger";
children: ReactNode; children: ReactNode;
icon?: LucideIcon; icon?: LucideIcon;
iconPosition?: "start" | "end";
} }
/** /**
* Renders a customizable button or link element styled with Tailwind CSS, supporting variants, icons, and disabled states. * Renders a customizable button or link element styled with Tailwind CSS, supporting variants, icons, and disabled states.
* *
* @param {ActionButtonProps} props - The component props. * @param {ActionButtonProps} props The component props.
* @returns {JSX.Element} The rendered button or link component. * @returns {JSX.Element} The rendered button or link component.
*/ */
export function ActionButton({ export function ActionButton({
@ -35,6 +38,7 @@ export function ActionButton({
variant = "primary", variant = "primary",
children, children,
icon: Icon, icon: Icon,
iconPosition = "start",
type = "button", type = "button",
disabled, disabled,
className = "", className = "",
@ -49,17 +53,20 @@ export function ActionButton({
secondary: secondary:
"bg-card text-foreground border border-foreground-muted/50 hover:bg-background-muted hover:border-foreground hover:-translate-y-0.5", "bg-card text-foreground border border-foreground-muted/50 hover:bg-background-muted hover:border-foreground hover:-translate-y-0.5",
danger: danger:
"bg-destructive/15 text-destructive border border-destructive/30 hover:bg-destructive hover:text-foreground hover.border-destructive hover:-translate-y-0.5", "bg-destructive/15 text-destructive border border-destructive/30 hover:bg-destructive hover:text-foreground hover:border-destructive hover:-translate-y-0.5",
}; };
const combinedStyles = `${baseStyles} ${variants[variant]} ${className}`; const combinedStyles = `${baseStyles} ${variants[variant]} ${className}`;
const renderIcon = Icon ? (
<Icon className="w-4 h-4 transition-transform duration-200 ease-out group-hover:scale-110 shrink-0" />
) : null;
const content = ( const content = (
<> <>
{Icon && ( {iconPosition === "start" && renderIcon}
<Icon className="w-4 h-4 transition-transform duration-200 ease-out group-hover:scale-110 shrink-0" />
)}
<span>{children}</span> <span>{children}</span>
{iconPosition === "end" && renderIcon}
</> </>
); );
@ -69,9 +76,6 @@ export function ActionButton({
<Link <Link
href={disabled ? "#" : href} href={disabled ? "#" : href}
aria-disabled={disabled} aria-disabled={disabled}
onClick={(e) => {
if (disabled) e.preventDefault();
}}
className={`${combinedStyles} ${ className={`${combinedStyles} ${
disabled ? "pointer-events-none opacity-50" : "" disabled ? "pointer-events-none opacity-50" : ""
}`} }`}