Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Commit

Permalink
feat: button component
Browse files Browse the repository at this point in the history
  • Loading branch information
hasan-deriv committed Oct 16, 2023
1 parent 270dad4 commit 1b34bc8
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 10 deletions.
48 changes: 41 additions & 7 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"clsx": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",
Expand Down
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import ErrorBoundary from 'Components/common/error-boundary';
import Layout from 'Components/layout';
import { Button } from 'Components/ui/button';
import AuthProvider from 'Contexts/authProvider';

const App = () => (
<AuthProvider>
<ErrorBoundary>
<Layout>App</Layout>
<Layout>
<Button disabled>add</Button>
</Layout>
</ErrorBoundary>
</AuthProvider>
);
Expand Down
112 changes: 112 additions & 0 deletions src/components/ui/button/__test__/button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'
import { Button } from '..';

describe("Button component", () => {
it('should render the button with children', async () => {
render(<Button>Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toBeInTheDocument();
});

it("should call the onClick handler when button is clicked", async () => {
const onClickMock = vi.fn();
render(<Button onClick={onClickMock}>Button</Button>);
const btnElement = screen.getByText("Button");
await userEvent.click(btnElement)
expect(onClickMock).toHaveBeenCalled()
})

it("should apply the default styling when no props are passed", () => {
render(<Button>Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("bg-primary", "text-white", "text-base")
});

it("should apply contained secondary styling when color prop is 'secondary' and variant prop is 'contained'", () => {
render(<Button variant="contained" color="secondary">Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("bg-secondary")
})

it("should apply outlined primary styling when color prop is 'primary' and variant prop is 'outlined'", () => {
render(<Button variant="outlined" color="primary">Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("bg-transparent", "border-primary")
})

it("should apply outlined secondary styling when color prop is 'secondary' and variant prop is 'outlined'", () => {
render(<Button variant="outlined" color="secondary">Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("bg-transparent", "border-secondary")
})

it("should apply outlined secondary styling when color prop is 'secondary' and variant prop is 'outlined'", () => {
render(<Button variant="outlined" color="secondary">Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("bg-transparent", "border-secondary")
})

it("should apply small size style when size prop is 'sm'", () => {
render(<Button size="sm">Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("px-3")
})

it("should apply medium size style when size prop is 'md'", () => {
render(<Button size="md">Button</Button>);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("text-[0.875rem]")
})

it("should have few same classes for size 'default' and 'md'", () => {
render(<div>
<Button size="default">Default Button</Button>
<Button size="md">Medium Button</Button>
</div>);
const defaultBtnElement = screen.getByText("Default Button");
const mediumBtnElement = screen.getByText("Medium Button");

const commonClass = "min-h-[2rem]"
expect(defaultBtnElement).toHaveClass(commonClass)
expect(mediumBtnElement).toHaveClass(commonClass)
})

it("should have same padding block classe for all size", () => {
render(<div>
<Button size="default">Default Button</Button>
<Button size="md">Medium Button</Button>
<Button size="sm">Small Button</Button>
</div>);
const defaultBtnElement = screen.getByText("Default Button");
const mediumBtnElement = screen.getByText("Medium Button");
const smallBtnElement = screen.getByText("Small Button");

const commonClass = "py-1"
expect(defaultBtnElement).toHaveClass(commonClass)
expect(mediumBtnElement).toHaveClass(commonClass)
expect(smallBtnElement).toHaveClass(commonClass)
})

it('should apply the fullwidth styling when fullwidth prop is true', () => {
render(<Button fullwidth={true}>Full Width Button</Button>)
const btnElement = screen.getByText("Full Width Button");
expect(btnElement).toHaveClass("w-full");
});

it('should apply custom class and styles correctly', () => {
render(
<Button className="custom-class" style={{ fontSize: "20px" }}>Button</Button>
);
const btnElement = screen.getByText("Button");
expect(btnElement).toHaveClass("custom-class");
expect(btnElement).toHaveStyle({ fontSize: "20px" });
});

it('should render a disabled button when disabled prop is true', () => {
const { getByText } = render(<Button disabled={true}>Click me</Button>);
const button = getByText('Click me');
expect(button).toBeDisabled();
});
})
87 changes: 87 additions & 0 deletions src/components/ui/button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "Utils/cn"

const buttonVariants = cva(
"inline-flex items-center justify-center rounded font-bold border border-solid transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-950 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
contained: "text-white",
outlined: "bg-transparent text-prominent"
},
color: {
primary: "border-primary hover:border-primary-hover focus:border-primary-hover",
secondary: "border-secondary",
success: "border-success",
},
size: {
default: "text-base",
sm: "text-[0.75rem] px-3",
md: "text-[0.875rem]",
},
fullwidth: {
true: "flex w-full",
},
},
compoundVariants: [
{
variant: "contained",
color: "primary",
className: "bg-primary hover:bg-primary-hover focus:bg-primary-hover"
},
{
variant: "contained",
color: "secondary",
className: "bg-secondary"
},
{
variant: "outlined",
color: "primary",
className: "hover:bg-primary-hover focus:bg-primary-hover"
},
{
variant: "outlined",
color: "secondary",
className: "hover:bg-secondary/[0.08] focus:bg-secondary/[0.08]"
},
{
size: ["default", "md"],
className: "px-5 min-h-[2rem] min-w-[3.5rem]"
},
{
size: ["default", "md", "sm"],
className: "py-1"
}
],
defaultVariants: {
variant: "contained",
color: "primary",
size: "default",
fullwidth: false
},
}
)

export interface ButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color">,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, color, fullwidth, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, color, fullwidth, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button, buttonVariants }
2 changes: 1 addition & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = {
100: "#e6e9e9",
200: "#d6d6d6"
},
prominent:" #333333",
prominent: "#333333",
"colored-barrier": "#008000",
active: "#d6dadb",
danger: "#ec3f3f",
Expand Down

0 comments on commit 1b34bc8

Please sign in to comment.