Skip to content

Commit

Permalink
Merge pull request #251 from Xcoder2023/feat/ISSUE-29-create-reusable…
Browse files Browse the repository at this point in the history
…-button-component

feat: create reusable button component
  • Loading branch information
SirhmVFX authored Jul 21, 2024
2 parents 9edbc9b + 3c622aa commit 1fa24cb
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 3 deletions.
117 changes: 117 additions & 0 deletions app/components/customButton/customButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Link } from "@remix-run/react";
import { LoaderCircle } from "lucide-react";
import React from "react";

type Variant =
| "default"
| "primary"
| "destructive"
| "subtle"
| "loading"
| "outline"
| "secondary"
| "ghost"
| "link";

type Size = "default" | "sm" | "lg" | "link" | "icon" | "circle";

interface ButtonProperties {
leftIcon?: React.ReactElement;
rightIcon?: React.ReactElement;
isLoading?: boolean;
href?: string;
variant?: Variant;
size?: Size;
icon?: React.ReactNode;
children?: React.ReactNode;
isIconOnly?: boolean;
isLeftIconVisible?: boolean;
isRightIconVisible?: boolean;
isDisabled?: boolean;
ariaLabel?: string;
onClick?: () => void;
className?: string;
}

const Button: React.FC<ButtonProperties> = ({
leftIcon,
rightIcon,
isLoading,
href,
variant = "default",
size = "default",
icon,
children,
isLeftIconVisible = true,
isRightIconVisible = true,
isDisabled,
ariaLabel,
onClick,
className = "",
}) => {
const baseClasses =
"inline-flex items-center justify-center px-[16px] py-[8px] gap-[8px] text-[14px] font-medium rounded-md";
const variantClasses = {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
primary: "bg-primary text-[#FFFFFF] hover:bg-[#F97316]",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
subtle: "bg-[#F1F5F9] hover:bg-[#E2E8F0]",
loading: "bg-[#0F172A] text-[#FFFFFF]",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
};
const sizeClasses = {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
link: "h-auto px-0",
icon: "h-10 w-10 !px-0",
circle: "h-[40px] w-[40px] !rounded-full !px-0",
};
const loadingClasses = "opacity-75 cursor-not-allowed";
const buttonClasses = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className} ${isLoading ? loadingClasses : ""}`;

const content = (
<>
{isLoading && <LoaderCircle />}
{isLeftIconVisible && leftIcon && !isLoading && (
<span className="">{leftIcon}</span>
)}
{children || icon}
{isRightIconVisible && rightIcon && (
<span className="text-primary">{rightIcon}</span>
)}
</>
);

if (href) {
return (
<Link
to={href}
className={buttonClasses}
aria-busy={isLoading}
aria-label={ariaLabel}
>
{content}
</Link>
);
}

return (
<button
className={buttonClasses}
onClick={onClick}
disabled={isLoading || isDisabled}
aria-busy={isLoading}
aria-label={ariaLabel}
>
{content}
</button>
);
};

export default Button;
4 changes: 1 addition & 3 deletions app/components/ui/footerCookieConsent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { FC } from "react";

import { Button } from "./button";

const handleAccept = () => {
Expand All @@ -14,7 +12,7 @@ const handleSettings = () => {
console.log("Cookie settings opened");
};

const FooterCookieConsent: FC = () => {
const FooterCookieConsent: React.FC = () => {
return (
<div className="fixed bottom-0 w-full border-t border-gray-200 bg-white p-6 text-gray-900">
<div className="flex flex-col items-start space-y-4 md:flex-row md:items-center md:justify-between md:space-x-4 md:space-y-0">
Expand Down
74 changes: 74 additions & 0 deletions guides/ButtonUsage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Button Component Usage Guide

The `Button` component is a versatile and customizable button component that supports various styles, sizes, and additional features. This guide provides detailed instructions on how to use the `Button` component.

## Props

- **leftIcon**: `React.ReactElement` (Optional)
- An element to be displayed on the left side of the button.

- **rightIcon**: `React.ReactElement` (Optional)
- An element to be displayed on the right side of the button.

- **isLoading**: `boolean` (Optional)
- If `true`, a loading spinner is displayed, and the button is disabled.

- **href**: `string` (Optional)
- A URL that converts the button into a link.

- **variant**: `Variant` (Optional, default: `"default"`)
- The style variant of the button. Options include:
- `"default"`
- `"primary"`
- `"destructive"`
- `"subtle"`
- `"loading"`
- `"outline"`
- `"secondary"`
- `"ghost"`
- `"link"`

- **size**: `Size` (Optional, default: `"default"`)
- The size of the button. Options include:
- `"default"`
- `"sm"`
- `"lg"`
- `"link"`
- `"icon"`
- `"circle"`

- **icon**: `React.ReactNode` (Optional)
- An icon or element to be displayed inside the button when `isIconOnly` is `true`.

- **children**: `React.ReactNode` (Optional)
- The content of the button.

- **isIconOnly**: `boolean` (Optional)
- If `true`, the button is styled as an icon-only button.

- **isLeftIconVisible**: `boolean` (Optional, default: `true`)
- If `false`, the left icon will not be displayed.

- **isRightIconVisible**: `boolean` (Optional, default: `true`)
- If `false`, the right icon will not be displayed.

- **isDisabled**: `boolean` (Optional)
- If `true`, the button is disabled.

- **ariaLabel**: `string` (Optional)
- An accessible label for the button.

- **onClick**: `() => void` (Optional)
- A function to handle the button's `onClick` event.

- **className**: `string` (Optional)
- Additional CSS classes for the button.

## Examples

### Basic Button

```tsx
<Button isLoading={true} variant="default" size="default">
Button CTA
</Button>
102 changes: 102 additions & 0 deletions guides/useButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Mail, Plus } from "lucide-react";
import { useState } from "react";

import Button from "~/components/customButton/customButton";

export default function Index() {
const [isLoading, setIsLoading] = useState(false);

const enterLoading = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 2000);
};

return (
<div className="flex h-screen items-center" style={{ fontFamily: "inter" }}>
<div className="mx-auto flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-[#9747FF] p-5">
<div className="grid grid-cols-2 gap-2 lg:flex">
<Button href="https://example.com" variant="primary" size="lg">
Button CTA
</Button>

<Button href="" variant="destructive" size="lg">
Button CTA
</Button>

<Button
isLoading={isLoading}
onClick={enterLoading}
variant="subtle"
size="lg"
>
Button CTA
</Button>

<Button href="" variant="ghost" size="lg">
Button CTA
</Button>

<Button
href=""
variant="primary"
size="lg"
leftIcon={<Mail width={16} />}
>
Button CTA
</Button>

<Button isLoading={true} variant="default" size="lg">
Button CTA
</Button>

<Button href="" variant="outline" size="lg">
Button CTA
</Button>

<Button href="" variant="ghost" size="lg">
Button CTA
</Button>

<Button
href=""
variant="outline"
size="icon"
icon={<Plus width={16} height={16} />}
/>

<Button
href=""
variant="outline"
size="circle"
icon={<Plus width={16} height={16} />}
/>
</div>

<div className="flex w-full justify-end gap-2 md:w-[83%]">
<Button
href=""
variant="link"
size="sm"
leftIcon={
<Mail className="text-[#F97316]" width={16} height={16} />
}
>
Button CTA
</Button>
<Button
href=""
variant="link"
size="sm"
rightIcon={
<Mail className="text-[#F97316]" width={16} height={16} />
}
>
Button CTA
</Button>
</div>
</div>
</div>
);
}

0 comments on commit 1fa24cb

Please sign in to comment.