Skip to content

Commit

Permalink
feat: add buttonlink component
Browse files Browse the repository at this point in the history
  • Loading branch information
ainunns committed Oct 9, 2024
1 parent 11c08ab commit f371164
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 30 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"vite": "^5.0"
},
"dependencies": {
"lucide-react": "^0.451.0"
"clsx": "^2.1.1",
"lucide-react": "^0.451.0",
"tailwind-merge": "^2.5.3"
}
}
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 79 additions & 29 deletions resources/js/Components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cn } from "@/Lib/utils";
import { Loader2, LucideIcon } from "lucide-react";
import type { ButtonHTMLAttributes, ReactNode } from "react";

Expand Down Expand Up @@ -37,38 +38,83 @@ export default function Button({
}: ButtonProps) {
const disabled = isLoading || buttonDisabled;

const baseClasses =
"inline-flex items-center justify-center rounded-lg font-medium focus:outline-none focus-visible:ring shadow-sm transition-colors duration-75 disabled:cursor-not-allowed";
const sizeClasses =
size === "lg"
? "min-h-[2.75rem] px-3.5 md:min-h-[3rem] text-base"
: size === "base"
? "min-h-[2.25rem] px-3 md:min-h-[2.5rem] text-sm md:text-base"
: "min-h-[1.75rem] px-2 md:min-h-[2rem] text-xs md:text-sm";

const variantClasses =
variant === "primary"
? "bg-primary-500 text-white border border-primary-600 hover:bg-primary-600 active:bg-primary-700 disabled:bg-primary-700 focus-visible:ring-primary-400"
: variant === "secondary"
? "bg-secondary-500 text-white border border-secondary-600 hover:bg-secondary-600 active:bg-secondary-700 disabled:bg-secondary-700 focus-visible:ring-secondary-400"
: variant === "danger"
? "bg-red-500 text-white border border-red-600 hover:bg-red-600 active:bg-red-700 disabled:bg-red-700 focus-visible:ring-red-400"
: variant === "warning"
? "bg-amber-500 text-white border border-amber-500 hover:bg-amber-600 active:bg-amber-700 disabled:bg-amber-700 focus-visible:ring-amber-400"
: variant === "outline"
? "text-typo border border-gray-300 hover:bg-light focus-visible:ring-primary-400 active:bg-typo-divider disabled:bg-typo-divider"
: "text-primary-500 shadow-none hover:bg-primary-50 focus-visible:ring-primary-400 active:bg-primary-100 disabled:bg-primary-100";

const loadingClasses = isLoading
? "relative text-transparent transition-none hover:text-transparent disabled:cursor-wait"
: "";

return (
<button
{...rest}
type="button"
disabled={disabled}
className={`${baseClasses} ${sizeClasses} ${variantClasses} ${loadingClasses} ${className}`}
className={cn(
"inline-flex items-center justify-center rounded-lg font-medium",
"focus:outline-none focus-visible:ring",
"shadow-sm",
"transition-colors duration-75",
//#region //*=========== Size ===========
[
size === "lg" && [
"min-h-[2.75rem] px-3.5 md:min-h-[3rem]",
"text-base",
],
size === "base" && [
"min-h-[2.25rem] px-3 md:min-h-[2.5rem]",
"text-sm md:text-base",
],
size === "sm" && [
"min-h-[1.75rem] px-2 md:min-h-[2rem]",
"text-xs md:text-sm",
],
],
//#endregion //*======== Size ===========
//#region //*=========== Variants ===========
[
variant === "primary" && [
"bg-primary-500 text-white",
"border border-primary-600",
"hover:bg-primary-600 hover:text-white",
"active:bg-primary-700",
"disabled:bg-primary-700",
"focus-visible:ring-primary-400",
],
variant === "secondary" && [
"bg-secondary-500 text-white",
"border border-secondary-600",
"hover:bg-secondary-600 hover:text-white",
"active:bg-secondary-700",
"disabled:bg-secondary-700",
"focus-visible:ring-secondary-400",
],
variant === "danger" && [
"bg-red-500 text-white",
"border border-red-600",
"hover:bg-red-600 hover:text-white",
"active:bg-red-700",
"disabled:bg-red-700",
"focus-visible:ring-red-400",
],
variant === "warning" && [
"bg-amber-500 text-white",
"border border-amber-500",
"hover:bg-amber-600 hover:text-white",
"active:bg-amber-700",
"disabled:bg-amber-700",
"focus-visible:ring-amber-400",
],
variant === "outline" && [
"text-typo",
"border border-gray-300",
"hover:bg-light focus-visible:ring-primary-400 active:bg-typo-divider disabled:bg-typo-divider",
],
variant === "ghost" && [
"text-primary-500",
"shadow-none",
"hover:bg-primary-50 focus-visible:ring-primary-400 active:bg-primary-100 disabled:bg-primary-100",
],
],
//#endregion //*======== Variants ===========
"disabled:cursor-not-allowed",
isLoading &&
"relative text-transparent transition-none hover:text-transparent disabled:cursor-wait",
className,
)}
>
{isLoading && (
<div
Expand All @@ -83,15 +129,19 @@ export default function Button({
)}
{LeftIcon && (
<div
className={`${size === "lg" ? "mr-3" : size === "base" ? "mr-2" : "mr-1"}`}
className={`${
size === "lg" ? "mr-3" : size === "base" ? "mr-2" : "mr-1"
}`}
>
<LeftIcon size="1em" className={`text-base ${leftIconClassName}`} />
</div>
)}
{children}
{RightIcon && (
<div
className={`${size === "lg" ? "ml-3" : size === "base" ? "ml-2" : "ml-1"}`}
className={`${
size === "lg" ? "ml-3" : size === "base" ? "ml-2" : "ml-1"
}`}
>
<RightIcon size="1em" className={`text-base ${rightIconClassName}`} />
</div>
Expand Down
141 changes: 141 additions & 0 deletions resources/js/Components/ButtonLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { LucideIcon } from "lucide-react";
import * as React from "react";

import UnstyledLink, { UnstyledLinkProps } from "@/Components/UnstyledLink";
import { cn } from "@/Lib/utils";

const ButtonLinkVariant = [
"primary",
"secondary",
"outline",
"ghost",
"warning",
] as const;
const ButtonLinkSize = ["sm", "base", "lg"] as const;

type ButtonLinkProps = {
variant?: (typeof ButtonLinkVariant)[number];
size?: (typeof ButtonLinkSize)[number];
leftIcon?: LucideIcon;
rightIcon?: LucideIcon;
leftIconClassName?: string;
rightIconClassName?: string;
} & UnstyledLinkProps;

const ButtonLink = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
(
{
children,
className,
variant = "primary",
size = "base",
leftIcon: LeftIcon,
rightIcon: RightIcon,
leftIconClassName,
rightIconClassName,
...rest
},
ref,
) => {
return (
<UnstyledLink
ref={ref}
{...rest}
className={cn(
"inline-flex items-center justify-center rounded-lg font-medium",
"focus:outline-none focus-visible:ring",
"shadow-sm",
"transition-colors duration-75",
//#region //*=========== Size ===========
[
size === "lg" && [
"min-h-[3rem] px-3.5 md:min-h-[2.75rem]",
"text-base",
],
size === "base" && [
"min-h-[2.25rem] px-3 md:min-h-[2.5rem]",
"text-sm md:text-base",
],
size === "sm" && [
"min-h-[1.75rem] px-2 md:min-h-[2rem]",
"text-xs md:text-sm",
],
],
//#endregion //*======== Size ===========
//#region //*=========== Variants ===========
[
variant === "primary" && [
"bg-primary-500 text-white",
"border border-primary-600",
"hover:bg-primary-600 hover:text-white",
"active:bg-primary-700",
"disabled:bg-primary-700",
"focus-visible:ring-primary-400",
],
variant === "secondary" && [
"bg-secondary-500 text-white",
"border border-secondary-600",
"hover:bg-secondary-600 hover:text-white",
"active:bg-secondary-700",
"disabled:bg-secondary-700",
"focus-visible:ring-secondary-400",
],
variant === "warning" && [
"bg-amber-500 text-white",
"border border-amber-500",
"hover:bg-amber-600 hover:text-white",
"active:bg-amber-700",
"disabled:bg-amber-700",
"focus-visible:ring-amber-400",
],
variant === "outline" && [
"text-typo",
"border border-gray-300",
"hover:bg-light focus-visible:ring-primary-400 active:bg-typo-divider disabled:bg-typo-divider",
],
variant === "ghost" && [
"text-primary-500",
"shadow-none",
"hover:bg-primary-50 focus-visible:ring-primary-400 active:bg-primary-100 disabled:bg-primary-100",
],
],
//#endregion //*======== Variants ===========
"disabled:cursor-not-allowed",
className,
)}
>
{LeftIcon && (
<div
className={cn([
size === "lg" && "mr-3",
size === "base" && "mr-2",
size === "sm" && "mr-1",
])}
>
<LeftIcon
size="1em"
className={cn("text-base", leftIconClassName)}
/>
</div>
)}
{children}
{RightIcon && (
<div
className={cn([
size === "lg" && "ml-3",
size === "base" && "ml-2",
size === "sm" && "ml-1",
])}
>
<RightIcon
size="1em"
className={cn("text-base", rightIconClassName)}
/>
</div>
)}
</UnstyledLink>
);
},
);

export default ButtonLink;
46 changes: 46 additions & 0 deletions resources/js/Components/UnstyledLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { cn } from "@/Lib/utils";
import { InertiaLinkProps, Link } from "@inertiajs/react";
import * as React from "react";

export type UnstyledLinkProps = {
href: string;
children: React.ReactNode;
openNewTab?: boolean;
className?: string;
inertiaLinkProps?: Omit<InertiaLinkProps, "href">;
} & React.ComponentPropsWithRef<"a">;

const UnstyledLink = React.forwardRef<HTMLAnchorElement, UnstyledLinkProps>(
(
{ children, href, openNewTab, className, inertiaLinkProps, ...rest },
ref,
) => {
const isNewTab =
openNewTab !== undefined
? openNewTab
: href && !href.startsWith("/") && !href.startsWith("#");

if (!isNewTab) {
return (
<Link href={href} ref={ref} className={className} {...inertiaLinkProps}>
{children}
</Link>
);
}

return (
<a
ref={ref}
target="_blank"
rel="noopener noreferrer"
href={href}
{...rest}
className={cn(className)}
>
{children}
</a>
);
},
);

export default UnstyledLink;
6 changes: 6 additions & 0 deletions resources/js/Lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

0 comments on commit f371164

Please sign in to comment.