aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-09-26 18:43:11 +0200
committerArmand Philippot <git@armandphilippot.com>2023-10-24 12:25:00 +0200
commit388e687857345c85ee550cd5da472675e05a6ff5 (patch)
tree0f035a3cad57a75959c028949a57227a83d480e2 /src/components/atoms
parent70efcfeaa0603415dd992cb662d8efb960e6e49a (diff)
refactor(components): rewrite Button and ButtonLink components
Both: * move styles to Sass placeholders Button: * add `isPressed` prop to Button * add `isLoading` prop to Button (to differentiate state from disabled) ButtonLink: * replace `external` prop with `isExternal` prop * replace `href` prop with `to` prop
Diffstat (limited to 'src/components/atoms')
-rw-r--r--src/components/atoms/buttons/button-link.test.tsx10
-rw-r--r--src/components/atoms/buttons/button-link.tsx55
-rw-r--r--src/components/atoms/buttons/button-link/button-link.module.scss29
-rw-r--r--src/components/atoms/buttons/button-link/button-link.stories.tsx (renamed from src/components/atoms/buttons/button-link.stories.tsx)57
-rw-r--r--src/components/atoms/buttons/button-link/button-link.test.tsx129
-rw-r--r--src/components/atoms/buttons/button-link/button-link.tsx67
-rw-r--r--src/components/atoms/buttons/button-link/index.ts1
-rw-r--r--src/components/atoms/buttons/button.test.tsx19
-rw-r--r--src/components/atoms/buttons/button/button.module.scss37
-rw-r--r--src/components/atoms/buttons/button/button.stories.tsx (renamed from src/components/atoms/buttons/button.stories.tsx)82
-rw-r--r--src/components/atoms/buttons/button/button.test.tsx133
-rw-r--r--src/components/atoms/buttons/button/button.tsx (renamed from src/components/atoms/buttons/button.tsx)41
-rw-r--r--src/components/atoms/buttons/button/index.ts1
-rw-r--r--src/components/atoms/buttons/buttons.module.scss179
14 files changed, 478 insertions, 362 deletions
diff --git a/src/components/atoms/buttons/button-link.test.tsx b/src/components/atoms/buttons/button-link.test.tsx
deleted file mode 100644
index 8491101..0000000
--- a/src/components/atoms/buttons/button-link.test.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
-import { ButtonLink } from './button-link';
-
-describe('ButtonLink', () => {
- it('renders a ButtonLink component', () => {
- render(<ButtonLink target="#">Button Link</ButtonLink>);
- expect(screen.getByRole('link')).toHaveTextContent('Button Link');
- });
-});
diff --git a/src/components/atoms/buttons/button-link.tsx b/src/components/atoms/buttons/button-link.tsx
deleted file mode 100644
index c8180c9..0000000
--- a/src/components/atoms/buttons/button-link.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import Link from 'next/link';
-import { AnchorHTMLAttributes, FC, ReactNode } from 'react';
-import styles from './buttons.module.scss';
-
-export type ButtonLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
- /**
- * The button link body.
- */
- children: ReactNode;
- /**
- * True if it is an external link. Default: false.
- */
- external?: boolean;
- /**
- * ButtonLink kind. Default: secondary.
- */
- kind?: 'primary' | 'secondary' | 'tertiary';
- /**
- * ButtonLink shape. Default: rectangle.
- */
- shape?: 'circle' | 'rectangle' | 'square';
- /**
- * Define an URL as target.
- */
- target: string;
-};
-
-/**
- * ButtonLink component
- *
- * Use a button-like link as call to action.
- */
-export const ButtonLink: FC<ButtonLinkProps> = ({
- children,
- className,
- target,
- kind = 'secondary',
- shape = 'rectangle',
- external = false,
- ...props
-}) => {
- const kindClass = styles[`btn--${kind}`];
- const shapeClass = styles[`btn--${shape}`];
- const btnClass = `${styles.btn} ${kindClass} ${shapeClass} ${className}`;
-
- return external ? (
- <a {...props} className={btnClass} href={target}>
- {children}
- </a>
- ) : (
- <Link {...props} className={btnClass} href={target}>
- {children}
- </Link>
- );
-};
diff --git a/src/components/atoms/buttons/button-link/button-link.module.scss b/src/components/atoms/buttons/button-link/button-link.module.scss
new file mode 100644
index 0000000..0f35a24
--- /dev/null
+++ b/src/components/atoms/buttons/button-link/button-link.module.scss
@@ -0,0 +1,29 @@
+@use "../../../../styles/abstracts/placeholders";
+
+.btn {
+ @extend %button;
+
+ &--circle {
+ @extend %circle-button;
+ }
+
+ &--rectangle {
+ @extend %rectangle-button;
+ }
+
+ &--square {
+ @extend %square-button;
+ }
+
+ &--primary {
+ @extend %primary-button;
+ }
+
+ &--secondary {
+ @extend %secondary-button;
+ }
+
+ &--tertiary {
+ @extend %tertiary-button;
+ }
+}
diff --git a/src/components/atoms/buttons/button-link.stories.tsx b/src/components/atoms/buttons/button-link/button-link.stories.tsx
index 32c2a7f..f048ce9 100644
--- a/src/components/atoms/buttons/button-link.stories.tsx
+++ b/src/components/atoms/buttons/button-link/button-link.stories.tsx
@@ -1,4 +1,4 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { ButtonLink } from './button-link';
/**
@@ -8,36 +8,10 @@ export default {
title: 'Atoms/Buttons/ButtonLink',
component: ButtonLink,
args: {
- external: false,
+ isExternal: false,
shape: 'rectangle',
},
argTypes: {
- 'aria-label': {
- control: {
- type: 'text',
- },
- description: 'An accessible label.',
- table: {
- category: 'Accessibility',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- 'aria-labelledby': {
- control: {
- type: null,
- },
- description: 'One or more ids that refer to an accessible label.',
- table: {
- category: 'Accessibility',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
children: {
control: {
type: 'text',
@@ -48,20 +22,7 @@ export default {
required: true,
},
},
- className: {
- control: {
- type: 'text',
- },
- description: 'Set additional classnames to the button link.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- external: {
+ isExternal: {
control: {
type: 'boolean',
},
@@ -95,7 +56,7 @@ export default {
type: 'select',
},
description: 'The link shape.',
- options: ['rectangle', 'square'],
+ options: ['circle', 'rectangle', 'square'],
table: {
category: 'Options',
defaultValue: { summary: 'rectangle' },
@@ -105,9 +66,9 @@ export default {
required: false,
},
},
- target: {
+ to: {
control: {
- type: null,
+ type: 'text',
},
description: 'The link target.',
type: {
@@ -129,7 +90,7 @@ export const Primary = Template.bind({});
Primary.args = {
children: 'Link',
kind: 'primary',
- target: '#',
+ to: '#',
};
/**
@@ -139,7 +100,7 @@ export const Secondary = Template.bind({});
Secondary.args = {
children: 'Link',
kind: 'secondary',
- target: '#',
+ to: '#',
};
/**
@@ -149,5 +110,5 @@ export const Tertiary = Template.bind({});
Tertiary.args = {
children: 'Link',
kind: 'tertiary',
- target: '#',
+ to: '#',
};
diff --git a/src/components/atoms/buttons/button-link/button-link.test.tsx b/src/components/atoms/buttons/button-link/button-link.test.tsx
new file mode 100644
index 0000000..d18120b
--- /dev/null
+++ b/src/components/atoms/buttons/button-link/button-link.test.tsx
@@ -0,0 +1,129 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { ButtonLink } from './button-link';
+
+describe('ButtonLink', () => {
+ it('renders a link with anchor and href', () => {
+ const target = 'eum';
+ const body = 'est eaque nostrum';
+
+ render(<ButtonLink to={target}>{body}</ButtonLink>);
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveAttribute(
+ 'href',
+ target
+ );
+ });
+
+ it('renders an external link', () => {
+ const target = 'voluptatem';
+ const body = 'impedit';
+
+ render(
+ <ButtonLink isExternal to={target}>
+ {body}
+ </ButtonLink>
+ );
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveAttribute(
+ 'rel',
+ expect.stringContaining('external')
+ );
+ });
+
+ it('renders a primary button', () => {
+ const target = 'vero';
+ const body = 'iure';
+
+ render(
+ // eslint-disable-next-line react/jsx-no-literals -- Ignore kind.
+ <ButtonLink kind="primary" to={target}>
+ {body}
+ </ButtonLink>
+ );
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveClass(
+ 'btn--primary'
+ );
+ });
+
+ it('renders a secondary button', () => {
+ const target = 'voluptatem';
+ const body = 'et';
+
+ render(
+ // eslint-disable-next-line react/jsx-no-literals -- Ignore kind.
+ <ButtonLink kind="secondary" to={target}>
+ {body}
+ </ButtonLink>
+ );
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveClass(
+ 'btn--secondary'
+ );
+ });
+
+ it('renders a tertiary button', () => {
+ const target = 'vitae';
+ const body = 'quo';
+
+ render(
+ // eslint-disable-next-line react/jsx-no-literals -- Ignore kind.
+ <ButtonLink kind="tertiary" to={target}>
+ {body}
+ </ButtonLink>
+ );
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveClass(
+ 'btn--tertiary'
+ );
+ });
+
+ it('renders a circle button', () => {
+ const target = 'praesentium';
+ const body = 'laudantium';
+
+ render(
+ // eslint-disable-next-line react/jsx-no-literals -- Ignore kind.
+ <ButtonLink shape="circle" to={target}>
+ {body}
+ </ButtonLink>
+ );
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveClass(
+ 'btn--circle'
+ );
+ });
+
+ it('renders a rectangle button', () => {
+ const target = 'tempora';
+ const body = 'ut';
+
+ render(
+ // eslint-disable-next-line react/jsx-no-literals -- Ignore kind.
+ <ButtonLink shape="rectangle" to={target}>
+ {body}
+ </ButtonLink>
+ );
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveClass(
+ 'btn--rectangle'
+ );
+ });
+
+ it('renders a square button', () => {
+ const target = 'quia';
+ const body = 'non';
+
+ render(
+ // eslint-disable-next-line react/jsx-no-literals -- Ignore kind.
+ <ButtonLink shape="square" to={target}>
+ {body}
+ </ButtonLink>
+ );
+
+ expect(rtlScreen.getByRole('link', { name: body })).toHaveClass(
+ 'btn--square'
+ );
+ });
+});
diff --git a/src/components/atoms/buttons/button-link/button-link.tsx b/src/components/atoms/buttons/button-link/button-link.tsx
new file mode 100644
index 0000000..f8bbadc
--- /dev/null
+++ b/src/components/atoms/buttons/button-link/button-link.tsx
@@ -0,0 +1,67 @@
+import Link from 'next/link';
+import type { AnchorHTMLAttributes, FC, ReactNode } from 'react';
+import styles from './button-link.module.scss';
+
+export type ButtonLinkProps = Omit<
+ AnchorHTMLAttributes<HTMLAnchorElement>,
+ 'href'
+> & {
+ /**
+ * The button link body.
+ */
+ children: ReactNode;
+ /**
+ * True if it is an external link.
+ *
+ * @default false
+ */
+ isExternal?: boolean;
+ /**
+ * Define the button kind.
+ *
+ * @default 'secondary'
+ */
+ kind?: 'primary' | 'secondary' | 'tertiary';
+ /**
+ * Define the button shape.
+ *
+ * @default 'rectangle'
+ */
+ shape?: 'circle' | 'rectangle' | 'square';
+ /**
+ * Define an URL or anchor as target.
+ */
+ to: string;
+};
+
+/**
+ * ButtonLink component
+ *
+ * Use a button-like link as call to action.
+ */
+export const ButtonLink: FC<ButtonLinkProps> = ({
+ children,
+ className = '',
+ kind = 'secondary',
+ shape = 'rectangle',
+ isExternal = false,
+ rel = '',
+ to,
+ ...props
+}) => {
+ const kindClass = styles[`btn--${kind}`];
+ const shapeClass = styles[`btn--${shape}`];
+ const btnClass = `${styles.btn} ${kindClass} ${shapeClass} ${className}`;
+ const linkRel =
+ isExternal && !rel.includes('external') ? `external ${rel}` : rel;
+
+ return isExternal ? (
+ <a {...props} className={btnClass} href={to} rel={linkRel}>
+ {children}
+ </a>
+ ) : (
+ <Link {...props} className={btnClass} href={to} rel={rel}>
+ {children}
+ </Link>
+ );
+};
diff --git a/src/components/atoms/buttons/button-link/index.ts b/src/components/atoms/buttons/button-link/index.ts
new file mode 100644
index 0000000..68d0a03
--- /dev/null
+++ b/src/components/atoms/buttons/button-link/index.ts
@@ -0,0 +1 @@
+export * from './button-link';
diff --git a/src/components/atoms/buttons/button.test.tsx b/src/components/atoms/buttons/button.test.tsx
deleted file mode 100644
index b6bfc5d..0000000
--- a/src/components/atoms/buttons/button.test.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
-import { Button } from './button';
-
-describe('Button', () => {
- it('renders the Button component', () => {
- render(<Button onClick={() => null}>Button</Button>);
- expect(screen.getByRole('button')).toBeInTheDocument();
- });
-
- it('renders the Button component with disabled state', () => {
- render(
- <Button onClick={() => null} disabled={true}>
- Disabled Button
- </Button>
- );
- expect(screen.getByRole('button')).toBeDisabled();
- });
-});
diff --git a/src/components/atoms/buttons/button/button.module.scss b/src/components/atoms/buttons/button/button.module.scss
new file mode 100644
index 0000000..508ff9a
--- /dev/null
+++ b/src/components/atoms/buttons/button/button.module.scss
@@ -0,0 +1,37 @@
+@use "../../../../styles/abstracts/placeholders";
+
+.btn {
+ @extend %button;
+
+ &--initial {
+ border-radius: 0;
+ }
+
+ &--circle {
+ @extend %circle-button;
+ }
+
+ &--rectangle {
+ @extend %rectangle-button;
+ }
+
+ &--square {
+ @extend %square-button;
+ }
+
+ &--neutral {
+ background: inherit;
+ }
+
+ &--primary {
+ @extend %primary-button;
+ }
+
+ &--secondary {
+ @extend %secondary-button;
+ }
+
+ &--tertiary {
+ @extend %tertiary-button;
+ }
+}
diff --git a/src/components/atoms/buttons/button.stories.tsx b/src/components/atoms/buttons/button/button.stories.tsx
index ba09a0d..5ce28fb 100644
--- a/src/components/atoms/buttons/button.stories.tsx
+++ b/src/components/atoms/buttons/button/button.stories.tsx
@@ -1,4 +1,4 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { Button } from './button';
/**
@@ -8,51 +8,53 @@ export default {
title: 'Atoms/Buttons/Button',
component: Button,
args: {
- disabled: false,
type: 'button',
},
argTypes: {
- 'aria-label': {
+ children: {
control: {
type: 'text',
},
- description: 'An accessible label.',
- table: {
- category: 'Accessibility',
- },
+ description: 'The button body.',
type: {
name: 'string',
- required: false,
+ required: true,
},
},
- children: {
+ isDisabled: {
control: {
- type: 'text',
+ type: 'boolean',
+ },
+ description: 'Should the button be disabled?',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
},
- description: 'The button body.',
type: {
- name: 'string',
- required: true,
+ name: 'boolean',
+ required: false,
},
},
- className: {
+ isLoading: {
control: {
- type: 'text',
+ type: 'boolean',
},
- description: 'Set additional classnames to the button wrapper.',
+ description:
+ 'Should the button be disabled because it is loading something?',
table: {
- category: 'Styles',
+ category: 'Options',
+ defaultValue: { summary: false },
},
type: {
- name: 'string',
+ name: 'boolean',
required: false,
},
},
- disabled: {
+ isPressed: {
control: {
type: 'boolean',
},
- description: 'Render button as disabled.',
+ description: 'Define if the button is currently pressed.',
table: {
category: 'Options',
defaultValue: { summary: false },
@@ -123,28 +125,10 @@ export default {
},
} as ComponentMeta<typeof Button>;
-const Template: ComponentStory<typeof Button> = (args) => {
- const { children, type, ...props } = args;
-
- const getBody = () => {
- if (children) return children;
+const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
- switch (type) {
- case 'reset':
- return 'Reset';
- case 'submit':
- return 'Submit';
- case 'button':
- default:
- return 'Button';
- }
- };
-
- return (
- <Button type={type} {...props}>
- {getBody()}
- </Button>
- );
+const logClick = () => {
+ console.log('Button has been clicked!');
};
/**
@@ -152,7 +136,9 @@ const Template: ComponentStory<typeof Button> = (args) => {
*/
export const Primary = Template.bind({});
Primary.args = {
+ children: 'Click on the button',
kind: 'primary',
+ onClick: logClick,
};
/**
@@ -160,7 +146,9 @@ Primary.args = {
*/
export const Secondary = Template.bind({});
Secondary.args = {
+ children: 'Click on the button',
kind: 'secondary',
+ onClick: logClick,
};
/**
@@ -168,5 +156,17 @@ Secondary.args = {
*/
export const Tertiary = Template.bind({});
Tertiary.args = {
+ children: 'Click on the button',
kind: 'tertiary',
+ onClick: logClick,
+};
+
+/**
+ * Button Story - Neutral
+ */
+export const Neutral = Template.bind({});
+Neutral.args = {
+ children: 'Click on the button',
+ kind: 'neutral',
+ onClick: logClick,
};
diff --git a/src/components/atoms/buttons/button/button.test.tsx b/src/components/atoms/buttons/button/button.test.tsx
new file mode 100644
index 0000000..f7de1b3
--- /dev/null
+++ b/src/components/atoms/buttons/button/button.test.tsx
@@ -0,0 +1,133 @@
+/* eslint-disable max-statements */
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { Button } from './button';
+
+describe('Button', () => {
+ it('renders the button body', () => {
+ const body = 'aliquid';
+
+ render(<Button>{body}</Button>);
+ expect(rtlScreen.getByRole('button')).toHaveTextContent(body);
+ });
+
+ it('renders a disabled button', () => {
+ const body = 'quod';
+
+ render(<Button isDisabled>{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toBeDisabled();
+ });
+
+ it('renders a button currently loading something', () => {
+ const body = 'quod';
+
+ render(<Button isLoading>{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { busy: true })).toHaveAccessibleName(
+ body
+ );
+ });
+
+ it('renders a pressed button', () => {
+ const body = 'quod';
+
+ render(<Button isPressed>{body}</Button>);
+
+ expect(
+ rtlScreen.getByRole('button', { pressed: true })
+ ).toHaveAccessibleName(body);
+ });
+
+ it('renders a submit button', () => {
+ const body = 'dolorum';
+
+ render(<Button type="submit">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveAttribute(
+ 'type',
+ 'submit'
+ );
+ });
+
+ it('renders a reset button', () => {
+ const body = 'consectetur';
+
+ render(<Button type="reset">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveAttribute(
+ 'type',
+ 'reset'
+ );
+ });
+
+ it('renders a primary button', () => {
+ const body = 'iure';
+
+ render(<Button kind="primary">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveClass(
+ 'btn--primary'
+ );
+ });
+
+ it('renders a secondary button', () => {
+ const body = 'et';
+
+ render(<Button kind="secondary">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveClass(
+ 'btn--secondary'
+ );
+ });
+
+ it('renders a tertiary button', () => {
+ const body = 'quo';
+
+ render(<Button kind="tertiary">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveClass(
+ 'btn--tertiary'
+ );
+ });
+
+ it('renders a neutral button', () => {
+ const body = 'voluptatem';
+
+ render(<Button kind="neutral">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveClass(
+ 'btn--neutral'
+ );
+ });
+
+ it('renders a circle button', () => {
+ const body = 'laudantium';
+
+ render(<Button shape="circle">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveClass(
+ 'btn--circle'
+ );
+ });
+
+ it('renders a rectangle button', () => {
+ const body = 'ut';
+
+ render(<Button shape="rectangle">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveClass(
+ 'btn--rectangle'
+ );
+ });
+
+ it('renders a square button', () => {
+ const body = 'non';
+
+ render(<Button shape="square">{body}</Button>);
+
+ expect(rtlScreen.getByRole('button', { name: body })).toHaveClass(
+ 'btn--square'
+ );
+ });
+});
diff --git a/src/components/atoms/buttons/button.tsx b/src/components/atoms/buttons/button/button.tsx
index 6ef5775..8489b31 100644
--- a/src/components/atoms/buttons/button.tsx
+++ b/src/components/atoms/buttons/button/button.tsx
@@ -1,22 +1,37 @@
import {
- ButtonHTMLAttributes,
+ type ButtonHTMLAttributes,
forwardRef,
- ForwardRefRenderFunction,
- ReactNode,
+ type ForwardRefRenderFunction,
+ type ReactNode,
} from 'react';
-import styles from './buttons.module.scss';
+import styles from './button.module.scss';
-export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
+export type ButtonProps = Omit<
+ ButtonHTMLAttributes<HTMLButtonElement>,
+ 'aria-busy' | 'aria-disabled' | 'aria-pressed' | 'aria-selected' | 'disabled'
+> & {
/**
* The button body.
*/
children: ReactNode;
/**
- * Button state.
+ * Should the button be disabled?
*
- * @default false
+ * @default undefined
*/
- disabled?: boolean;
+ isDisabled?: boolean;
+ /**
+ * Is the button already executing some action?
+ *
+ * @default undefined
+ */
+ isLoading?: boolean;
+ /**
+ * Is the button a toggle and is it currently pressed?
+ *
+ * @default undefined
+ */
+ isPressed?: boolean;
/**
* Button kind.
*
@@ -44,7 +59,9 @@ const ButtonWithRef: ForwardRefRenderFunction<
{
className = '',
children,
- disabled = false,
+ isPressed,
+ isDisabled,
+ isLoading,
kind = 'secondary',
shape = 'rectangle',
type = 'button',
@@ -59,9 +76,13 @@ const ButtonWithRef: ForwardRefRenderFunction<
return (
<button
{...props}
+ aria-busy={isLoading}
+ aria-disabled={isDisabled}
+ aria-pressed={isPressed}
className={btnClass}
- disabled={disabled}
+ disabled={isDisabled ?? isLoading}
ref={ref}
+ // eslint-disable-next-line react/button-has-type -- Default value is set.
type={type}
>
{children}
diff --git a/src/components/atoms/buttons/button/index.ts b/src/components/atoms/buttons/button/index.ts
new file mode 100644
index 0000000..eaf5eea
--- /dev/null
+++ b/src/components/atoms/buttons/button/index.ts
@@ -0,0 +1 @@
+export * from './button';
diff --git a/src/components/atoms/buttons/buttons.module.scss b/src/components/atoms/buttons/buttons.module.scss
deleted file mode 100644
index a46f55c..0000000
--- a/src/components/atoms/buttons/buttons.module.scss
+++ /dev/null
@@ -1,179 +0,0 @@
-@use "../../../styles/abstracts/functions" as fun;
-
-.btn {
- display: inline-flex;
- place-content: center;
- align-items: center;
- border: none;
- border-radius: fun.convert-px(5);
- font-size: var(--font-size-md);
- font-weight: 600;
- text-decoration: none;
- transition: all 0.3s ease-in-out 0s;
-
- &--initial {
- border-radius: 0;
- }
-
- &--rectangle {
- padding: var(--spacing-2xs) var(--spacing-sm);
- }
-
- &--square,
- &--circle {
- min-width: fit-content;
- min-height: fit-content;
- padding: var(--spacing-xs);
- aspect-ratio: 1 / 1;
- }
-
- &--circle {
- border-radius: 50%;
- }
-
- &:disabled {
- cursor: wait;
- }
-
- &--neutral {
- background: inherit;
- }
-
- &--primary {
- background: var(--color-primary);
- border: fun.convert-px(2) solid var(--color-bg);
- box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary),
- 0 0 0 fun.convert-px(3) var(--color-primary-darker),
- fun.convert-px(2) fun.convert-px(2) 0 fun.convert-px(3)
- var(--color-primary-dark);
- color: var(--color-fg-inverted);
- text-shadow: fun.convert-px(2) fun.convert-px(2) 0 var(--color-shadow);
-
- &:disabled {
- background: var(--color-primary-darker);
- }
-
- &:not(:disabled) {
- &:hover,
- &:focus {
- background: var(--color-primary-light);
- box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary-light),
- 0 0 0 fun.convert-px(3) var(--color-primary-darker),
- fun.convert-px(7) fun.convert-px(7) 0 fun.convert-px(2)
- var(--color-primary-dark);
- color: var(--color-fg-inverted);
- transform: translateX(#{fun.convert-px(-4)})
- translateY(#{fun.convert-px(-4)});
- }
-
- &:focus {
- text-decoration: underline solid var(--color-fg-inverted)
- fun.convert-px(2);
- }
-
- &:active {
- background: var(--color-primary-dark);
- box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary),
- 0 0 0 fun.convert-px(3) var(--color-primary-darker),
- 0 0 0 0 var(--color-primary-dark);
- text-decoration: none;
- transform: translateX(#{fun.convert-px(4)})
- translateY(#{fun.convert-px(4)});
- }
- }
- }
-
- &--secondary {
- background: var(--color-bg);
- border: fun.convert-px(3) solid var(--color-primary);
- box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1)
- var(--color-shadow),
- fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2)
- var(--color-shadow),
- fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4)
- var(--color-shadow);
- color: var(--color-primary);
-
- &:disabled {
- border-color: var(--color-border-dark);
- color: var(--color-fg-light);
- }
-
- &:not(:disabled) {
- &:hover,
- &:focus {
- border-color: var(--color-primary-light);
- color: var(--color-primary-light);
- box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1)
- var(--color-shadow-light),
- fun.convert-px(1) fun.convert-px(2) fun.convert-px(2)
- fun.convert-px(-2) var(--color-shadow-light),
- fun.convert-px(3) fun.convert-px(4) fun.convert-px(5)
- fun.convert-px(-4) var(--color-shadow-light),
- fun.convert-px(7) fun.convert-px(10) fun.convert-px(12)
- fun.convert-px(-3) var(--color-shadow-light);
- transform: scale(var(--scale-up, 1.1));
- }
-
- &:focus {
- text-decoration: underline var(--color-primary-light) fun.convert-px(3);
- }
-
- &:active {
- border-color: var(--color-primary-dark);
- box-shadow: 0 0 0 0 var(--color-shadow);
- color: var(--color-primary-dark);
- text-decoration: none;
- transform: scale(var(--scale-down, 0.94));
- }
- }
- }
-
- &--tertiary {
- background: var(--color-bg);
- border: fun.convert-px(3) solid var(--color-primary);
- box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 var(--color-bg),
- fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-primary-dark),
- fun.convert-px(5) fun.convert-px(5) 0 0 var(--color-bg),
- fun.convert-px(6) fun.convert-px(6) 0 0 var(--color-primary-dark);
- color: var(--color-primary);
-
- &:disabled {
- color: var(--color-fg-light);
- border-color: var(--color-border-dark);
- box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 var(--color-bg),
- fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-primary-darker),
- fun.convert-px(5) fun.convert-px(5) 0 0 var(--color-bg),
- fun.convert-px(6) fun.convert-px(6) 0 0 var(--color-primary-darker);
- }
-
- &:not(:disabled) {
- &:hover,
- &:focus {
- border-color: var(--color-primary-light);
- box-shadow: fun.convert-px(2) fun.convert-px(3) 0 0 var(--color-bg),
- fun.convert-px(4) fun.convert-px(5) 0 0 var(--color-primary),
- fun.convert-px(6) fun.convert-px(8) 0 0 var(--color-bg),
- fun.convert-px(8) fun.convert-px(10) 0 0 var(--color-primary),
- fun.convert-px(10) fun.convert-px(12) fun.convert-px(1) 0
- var(--color-shadow-light),
- fun.convert-px(10) fun.convert-px(12) fun.convert-px(5)
- fun.convert-px(1) var(--color-shadow-light);
- color: var(--color-primary-light);
- transform: translateX(#{fun.convert-px(-3)})
- translateY(#{fun.convert-px(-5)});
- }
-
- &:focus {
- text-decoration: underline var(--color-primary) fun.convert-px(2);
- }
-
- &:active {
- box-shadow: 0 0 0 0 var(--color-shadow);
- text-decoration: none;
- transform: translateX(#{fun.convert-px(5)})
- translateY(#{fun.convert-px(6)});
- }
- }
- }
-}