diff --git a/__tests__/components/Button/Button.test.tsx b/__tests__/components/Button/Button.test.tsx
new file mode 100644
index 00000000..4ef4e298
--- /dev/null
+++ b/__tests__/components/Button/Button.test.tsx
@@ -0,0 +1,133 @@
+import { render } from '@testing-library/react'
+
+import Button from 'components/Button'
+import {
+ buttonColorClasses,
+ buttonSizeClasses,
+ buttonVariantClasses,
+ focusClasses,
+} from 'components/Button/constants'
+
+describe('', () => {
+ afterAll(() => {
+ jest.resetAllMocks()
+ })
+
+ it('should render', () => {
+ const { container } = render()
+ expect(container).toBeInTheDocument()
+ })
+
+ it('should render `children` when its passed', () => {
+ const children = Hello World!
+ const { getByTestId } = render()
+
+ expect(getByTestId('test-id')).toBeInTheDocument()
+ })
+
+ it('should handle `className` prop correctly', () => {
+ const testClass = 'test-class'
+ const { container } = render()
+
+ expect(container.querySelector('button')).toHaveClass(testClass)
+ })
+
+ it('should handle `color` prop correctly', () => {
+ const colors = Object.keys(buttonColorClasses) as [keyof typeof buttonColorClasses]
+
+ colors.forEach((color) => {
+ const { container } = render()
+
+ expect(container.querySelector('button')).toHaveClass(buttonColorClasses[color])
+ })
+ })
+
+ it('should handle `disabled=true` prop correctly', () => {
+ const testFunction = jest.fn()
+ const { container } = render()
+ const button = container.querySelector('button')
+
+ button?.click()
+
+ expect(button).toHaveClass('pointer-events-none')
+ expect(testFunction).not.toBeCalled()
+ })
+
+ it('should handle `disabled=false` prop correctly', () => {
+ const testFunction = jest.fn()
+ const { container } = render()
+ const button = container.querySelector('button')
+
+ button?.click()
+
+ expect(button).not.toHaveClass('pointer-events-none')
+ expect(testFunction).toBeCalled()
+ })
+
+ it('should show progress indicator when `showProgressIndicator=true`', () => {
+ const { getByTestId } = render()
+
+ expect(getByTestId('circular-progress-component')).toBeInTheDocument()
+ })
+
+ it('should handle `size` prop correctly', () => {
+ const sizes = Object.keys(buttonSizeClasses) as [keyof typeof buttonSizeClasses]
+
+ sizes.forEach((size) => {
+ const { container } = render()
+
+ expect(container.querySelector('button')).toHaveClass(buttonSizeClasses[size])
+ })
+ })
+
+ it('should show `text` when its passed', () => {
+ const text = 'Hello!'
+ const { getByText } = render()
+
+ expect(getByText(text)).toBeInTheDocument()
+ })
+
+ it('should handle `variant` prop correctly', () => {
+ const variants = Object.keys(buttonVariantClasses) as [keyof typeof buttonVariantClasses]
+
+ variants.forEach((variant) => {
+ const { container } = render()
+
+ expect(container.querySelector('button')).toHaveClass(buttonVariantClasses[variant])
+ })
+ })
+
+ it('should show left icon when `leftIcon` prop is passed', () => {
+ const icon = this is the left icon
+ const { getByTestId } = render()
+
+ expect(getByTestId('left-icon')).toBeInTheDocument()
+ })
+
+ it('should show right icon when `rightIcon` prop is passed', () => {
+ const icon = this is the right icon
+ const { getByTestId } = render()
+
+ expect(getByTestId('right-icon')).toBeInTheDocument()
+ })
+
+ it('should handle `iconClassName` prop correctly', () => {
+ const icon = just an icon
+ const { getByTestId } = render()
+
+ expect(getByTestId('icon').parentElement).toHaveClass('test-icon-class')
+ })
+
+ it('should show submenu indicator when `hasSubmenu=true`', () => {
+ const { getByTestId } = render()
+
+ expect(getByTestId('button-submenu-indicator')).toBeInTheDocument()
+ })
+
+ it('should set focus classes when `hasFocus=true`', () => {
+ const { container } = render()
+ const button = container.querySelector('button')
+
+ expect(button).toHaveClass(focusClasses['primary'])
+ })
+})
diff --git a/jest.config.js b/jest.config.js
index dcad7f3a..8d587646 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -9,6 +9,7 @@ module.exports = {
'!/.next/**',
'!/*.config.js',
'!/coverage/**',
+ '!/src/types/**',
],
moduleNameMapper: {
// Handle CSS imports (with CSS modules)
diff --git a/src/components/Account/AccountList.tsx b/src/components/Account/AccountList.tsx
index 5162283d..f09a3b98 100644
--- a/src/components/Account/AccountList.tsx
+++ b/src/components/Account/AccountList.tsx
@@ -3,7 +3,7 @@ import { useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import AccountStats from 'components/Account/AccountStats'
-import { Button } from 'components/Button'
+import Button from 'components/Button'
import Card from 'components/Card'
import { ArrowCircledTopRight, ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons'
import Radio from 'components/Radio'
diff --git a/src/components/Account/AccountMenuContent.tsx b/src/components/Account/AccountMenuContent.tsx
index 104355ce..9b273da4 100644
--- a/src/components/Account/AccountMenuContent.tsx
+++ b/src/components/Account/AccountMenuContent.tsx
@@ -5,7 +5,7 @@ import { useNavigate, useParams } from 'react-router-dom'
import AccountList from 'components/Account/AccountList'
import CreateAccount from 'components/Account/CreateAccount'
import FundAccount from 'components/Account/FundAccount'
-import { Button } from 'components/Button'
+import Button from 'components/Button'
import { CircularProgress } from 'components/CircularProgress'
import { Account, Plus, PlusCircled } from 'components/Icons'
import Overlay from 'components/Overlay'
diff --git a/src/components/Account/CreateAccount.tsx b/src/components/Account/CreateAccount.tsx
index 29f42bad..409146f0 100644
--- a/src/components/Account/CreateAccount.tsx
+++ b/src/components/Account/CreateAccount.tsx
@@ -1,4 +1,4 @@
-import { Button } from 'components/Button'
+import Button from 'components/Button'
import { ArrowRight } from 'components/Icons'
import Text from 'components/Text'
diff --git a/src/components/Account/FundAccount.tsx b/src/components/Account/FundAccount.tsx
index f2516cce..3751926a 100644
--- a/src/components/Account/FundAccount.tsx
+++ b/src/components/Account/FundAccount.tsx
@@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'
import { useCallback, useState } from 'react'
import { useParams } from 'react-router-dom'
-import { Button } from 'components/Button'
+import Button from 'components/Button'
import { ArrowRight, Cross } from 'components/Icons'
import SwitchWithLabel from 'components/SwitchWithLabel'
import Text from 'components/Text'
diff --git a/src/components/Borrow/AssetExpanded.tsx b/src/components/Borrow/AssetExpanded.tsx
index 21ddeeb0..0be6516b 100644
--- a/src/components/Borrow/AssetExpanded.tsx
+++ b/src/components/Borrow/AssetExpanded.tsx
@@ -1,6 +1,6 @@
import { Row } from '@tanstack/react-table'
-import { Button } from 'components/Button'
+import Button from 'components/Button'
import useStore from 'store'
import { getMarketAssets } from 'utils/assets'
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
deleted file mode 100644
index dfcd7ad0..00000000
--- a/src/components/Button.tsx
+++ /dev/null
@@ -1,205 +0,0 @@
-import classNames from 'classnames'
-import React, { LegacyRef, ReactElement, ReactNode } from 'react'
-
-import { CircularProgress } from 'components/CircularProgress'
-import { ChevronDown } from 'components/Icons'
-import useStore from 'store'
-
-interface Props {
- children?: string | ReactNode
- className?: string
- color?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'
- disabled?: boolean
- id?: string
- showProgressIndicator?: boolean
- size?: 'small' | 'medium' | 'large'
- text?: string | ReactNode
- variant?: 'solid' | 'transparent' | 'round'
- onClick?: (e: React.MouseEvent) => void
- leftIcon?: ReactElement
- rightIcon?: ReactElement
- iconClassName?: string
- hasSubmenu?: boolean
- hasFocus?: boolean
-}
-
-export const buttonColorClasses = {
- primary:
- 'font-bold gradient-primary-to-secondary hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
- secondary:
- 'border border-white/30 bg-transparent hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
- tertiary: 'bg-white/10 hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
- quaternary: 'bg-transparent text-white/60 hover:text-white ctive:text-white',
-}
-
-const focusClasses = {
- primary: 'bg-white/20',
- secondary: 'bg-white/20',
- tertiary: 'bg-white/20',
- quaternary: 'text-white',
-}
-
-const buttonBorderClasses =
- 'before:content-[" "] before:absolute before:inset-0 before:rounded-sm before:p-[1px] before:border-glas before:-z-1'
-
-const buttonGradientClasses = [
- 'before:content-[" "] before:absolute before:inset-0 before:rounded-sm before:-z-1 before:opacity-0',
- 'before:gradient-secondary-to-primary before:transition-opacity before:ease-in',
- 'hover:before:opacity-100',
-]
-
-const buttonTransparentColorClasses = {
- primary: 'hover:text-primary active:text-primary focus:text-primary',
- secondary: 'hover:text-secondary active:text-secondary focus:text-secondary',
- tertiary: 'hover:text-white/80 active:text-white/80 focus:text-white/80',
- quaternary: 'text-white/60 hover:text-white active:text-white',
-}
-
-const buttonRoundSizeClasses = {
- small: 'h-[32px] w-[32px]',
- medium: 'h-[40px] w-[40px]',
- large: 'h-[56px] w-[56px]',
-}
-
-export const buttonSizeClasses = {
- small: 'text-sm',
- medium: 'text-base',
- large: 'text-lg',
-}
-
-export const buttonPaddingClasses = {
- small: 'px-2.5 py-1.5 min-h-[32px]',
- medium: 'px-3 py-2 min-h-[40px]',
- large: 'px-3.5 py-2.5 min-h-[56px]',
-}
-
-export const buttonVariantClasses = {
- solid: 'rounded-sm text-white shadow-button justify-center group',
- transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
- round: 'rounded-full p-0',
-}
-
-function glowElement(enableAnimations: boolean) {
- return (
-
- )
-}
-
-export const Button = React.forwardRef(function Button(
- {
- children,
- className = '',
- color = 'primary',
- disabled,
- id = '',
- showProgressIndicator,
- size = 'small',
- text,
- variant = 'solid',
- onClick,
- leftIcon,
- rightIcon,
- iconClassName,
- hasSubmenu,
- hasFocus,
- }: Props,
- ref,
-) {
- const buttonClasses = []
- const enableAnimations = useStore((s) => s.enableAnimations)
- const isDisabled = disabled || showProgressIndicator
-
- switch (variant) {
- case 'round':
- buttonClasses.push(
- buttonSizeClasses[size],
- buttonRoundSizeClasses[size],
- buttonPaddingClasses[size],
- buttonColorClasses[color],
- )
- break
-
- case 'transparent':
- buttonClasses.push(buttonSizeClasses[size], buttonTransparentColorClasses[color])
- break
-
- case 'solid':
- buttonClasses.push(
- buttonSizeClasses[size],
- buttonPaddingClasses[size],
- buttonColorClasses[color],
- )
- break
- default:
- }
-
- return (
-
- )
-})
diff --git a/src/components/Button/constants.ts b/src/components/Button/constants.ts
new file mode 100644
index 00000000..06d738c7
--- /dev/null
+++ b/src/components/Button/constants.ts
@@ -0,0 +1,55 @@
+export const buttonColorClasses = {
+ primary:
+ 'font-bold gradient-primary-to-secondary hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
+ secondary:
+ 'border border-white/30 bg-transparent hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
+ tertiary: 'bg-white/10 hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
+ quaternary: 'bg-transparent text-white/60 hover:text-white active:text-white',
+}
+
+export const focusClasses = {
+ primary: 'bg-white/20',
+ secondary: 'bg-white/20',
+ tertiary: 'bg-white/20',
+ quaternary: 'text-white',
+}
+
+export const buttonBorderClasses =
+ 'before:content-[" "] before:absolute before:inset-0 before:rounded-sm before:p-[1px] before:border-glas before:-z-1'
+
+export const buttonGradientClasses = [
+ 'before:content-[" "] before:absolute before:inset-0 before:rounded-sm before:-z-1 before:opacity-0',
+ 'before:gradient-secondary-to-primary before:transition-opacity before:ease-in',
+ 'hover:before:opacity-100',
+]
+
+export const buttonTransparentColorClasses = {
+ primary: 'hover:text-primary active:text-primary focus:text-primary',
+ secondary: 'hover:text-secondary active:text-secondary focus:text-secondary',
+ tertiary: 'hover:text-white/80 active:text-white/80 focus:text-white/80',
+ quaternary: 'text-white/60 hover:text-white active:text-white',
+}
+
+export const buttonRoundSizeClasses = {
+ small: 'h-[32px] w-[32px]',
+ medium: 'h-[40px] w-[40px]',
+ large: 'h-[56px] w-[56px]',
+}
+
+export const buttonSizeClasses = {
+ small: 'text-sm',
+ medium: 'text-base',
+ large: 'text-lg',
+}
+
+export const buttonPaddingClasses = {
+ small: 'px-2.5 py-1.5 min-h-[32px]',
+ medium: 'px-3 py-2 min-h-[40px]',
+ large: 'px-3.5 py-2.5 min-h-[56px]',
+}
+
+export const buttonVariantClasses = {
+ solid: 'rounded-sm text-white shadow-button justify-center group',
+ transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
+ round: 'rounded-full p-0',
+}
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
new file mode 100644
index 00000000..59e18845
--- /dev/null
+++ b/src/components/Button/index.tsx
@@ -0,0 +1,127 @@
+import classNames from 'classnames'
+import React, { LegacyRef, ReactElement, ReactNode, useMemo } from 'react'
+
+import { CircularProgress } from 'components/CircularProgress'
+import {
+ buttonBorderClasses,
+ buttonColorClasses,
+ buttonGradientClasses,
+ buttonPaddingClasses,
+ buttonRoundSizeClasses,
+ buttonSizeClasses,
+ buttonTransparentColorClasses,
+ buttonVariantClasses,
+ focusClasses,
+} from 'components/Button/constants'
+import { glowElement } from 'components/Button/utils'
+import { ChevronDown } from 'components/Icons'
+import useStore from 'store'
+
+interface Props {
+ children?: string | ReactNode
+ className?: string
+ color?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'
+ disabled?: boolean
+ id?: string
+ showProgressIndicator?: boolean
+ size?: 'small' | 'medium' | 'large'
+ text?: string | ReactNode
+ variant?: 'solid' | 'transparent' | 'round'
+ onClick?: (e: React.MouseEvent) => void
+ leftIcon?: ReactElement
+ rightIcon?: ReactElement
+ iconClassName?: string
+ hasSubmenu?: boolean
+ hasFocus?: boolean
+}
+
+const Button = React.forwardRef(function Button(
+ {
+ children,
+ className = '',
+ color = 'primary',
+ disabled,
+ id = '',
+ showProgressIndicator,
+ size = 'small',
+ text,
+ variant = 'solid',
+ onClick,
+ leftIcon,
+ rightIcon,
+ iconClassName,
+ hasSubmenu,
+ hasFocus,
+ }: Props,
+ ref,
+) {
+ const enableAnimations = useStore((s) => s.enableAnimations)
+ const isDisabled = disabled || showProgressIndicator
+ const shouldShowText = text && !children
+ const shouldShowGlowElement = variant === 'solid' && !isDisabled
+
+ const buttonClassNames = useMemo(() => {
+ const buttonClasses = [
+ buttonSizeClasses[size],
+ buttonPaddingClasses[size],
+ buttonColorClasses[color],
+ ]
+
+ if (variant === 'round') {
+ buttonClasses.push(buttonColorClasses[color], buttonRoundSizeClasses[size])
+ } else if (variant === 'transparent') {
+ buttonClasses.push(buttonTransparentColorClasses[color])
+ }
+
+ return classNames(
+ 'relative z-1 flex items-center',
+ 'cursor-pointer appearance-none break-normal outline-none',
+ 'text-white transition-all',
+ enableAnimations && 'transition-color',
+ buttonClasses,
+ buttonVariantClasses[variant],
+ variant === 'solid' && color === 'tertiary' && buttonBorderClasses,
+ variant === 'solid' && color === 'primary' && buttonGradientClasses,
+ isDisabled && 'pointer-events-none opacity-50',
+ hasFocus && focusClasses[color],
+ className,
+ )
+ }, [className, color, enableAnimations, hasFocus, isDisabled, size, variant])
+
+ const [leftIconClassNames, rightIconClassNames] = useMemo(() => {
+ const hasContent = !!(text || children)
+ const iconClasses = ['flex items-center justify-center', iconClassName ?? 'h-4 w-4']
+ const leftIconClasses = [iconClasses, hasContent && 'mr-2']
+ const rightIconClasses = [iconClasses, hasContent && 'ml-2']
+
+ return [leftIconClasses, rightIconClasses].map(classNames)
+ }, [children, iconClassName, text])
+
+ return (
+
+ )
+})
+
+export default Button
diff --git a/src/components/Button/utils.tsx b/src/components/Button/utils.tsx
new file mode 100644
index 00000000..dc3e42d3
--- /dev/null
+++ b/src/components/Button/utils.tsx
@@ -0,0 +1,22 @@
+import classNames from 'classnames'
+
+export function glowElement(enableAnimations: boolean) {
+ return (
+
+ )
+}
diff --git a/src/components/CircularProgress.tsx b/src/components/CircularProgress.tsx
index 15fd9844..946a5105 100644
--- a/src/components/CircularProgress.tsx
+++ b/src/components/CircularProgress.tsx
@@ -31,7 +31,11 @@ export const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Pr
)
return (
-