diff --git a/.eslintrc.js b/.eslintrc.js
index 51be281..90d11fc 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -136,7 +136,7 @@ module.exports = {
settings: {
'import/resolver': {
node: {
- extensions: ['js', 'jsx', '.ts', '.tsx'],
+ extensions: ['js', 'jsx', '.ts', '.tsx', '.css'],
},
},
react: {
diff --git a/.storybook/preview.js b/.storybook/preview.js
index 48afd56..57a8213 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -1,3 +1,5 @@
+import '../src/shared/variables.css'
+
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
diff --git a/public/index.html b/public/index.html
index 0176762..f455f3c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -3,6 +3,9 @@
+
+
+
void
+ size: ButtonSize
+ variant: ButtonVariant
+ className?: string
+ // Wait for theme palette
+ color?: string
+ htmlType?: ButtonHtmlType
+}
+
+export const Button: FC = ({
+ color,
+ disabled,
+ onClick,
+ variant,
+ size,
+ className,
+ children,
+ htmlType,
+ isLoading,
+}) => {
+ const buttonStyles = classNames(
+ {
+ [styles.default]: true,
+ [styles.primary]: variant === ButtonVariant.primary,
+ [styles.outline]: variant === ButtonVariant.outline,
+ [styles.small]: size === ButtonSize.small,
+ [styles.withCustomColor]: !!color,
+ },
+ className
+ )
+
+ return (
+
+ )
+}
diff --git a/src/components/Button/Button.driver.tsx b/src/components/Button/Button.driver.tsx
new file mode 100644
index 0000000..acd85e2
--- /dev/null
+++ b/src/components/Button/Button.driver.tsx
@@ -0,0 +1,100 @@
+import React, { ReactElement } from 'react'
+
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { fireEvent, render, screen } from '@testing-library/react'
+import {
+ DataHooks as ButtonDataHooks,
+ ButtonSize,
+ ButtonVariant,
+ ButtonHtmlType,
+} from 'components/Button/constants'
+
+import { Button, PropsType } from './Button.component'
+
+export type DriverType = {
+ given: {
+ children: (children: ReactElement) => DriverType
+ className: (className?: string) => DriverType
+ color: (color?: string) => DriverType
+ disabled: (disabled: boolean) => DriverType
+ htmlType: (htmlType?: ButtonHtmlType) => DriverType
+ isLoading: (isLoading: boolean) => DriverType
+ onClick: (onClick: () => null) => DriverType
+ size: (size: ButtonSize) => DriverType
+ variant: (variant: ButtonVariant) => DriverType
+ }
+
+ then: {
+ clickButton: () => void
+ getButton: () => HTMLElement
+ isExistButton: () => boolean
+ isExistChildren: () => boolean
+ isLoadding: () => boolean
+ }
+
+ when: {
+ created: () => DriverType
+ }
+}
+
+export const createButtonDriver = (): DriverType => {
+ let props: PropsType
+
+ const driver: DriverType = {
+ given: {
+ children: (children: ReactElement) => {
+ props = { ...props, children }
+ return driver
+ },
+ color: (color?: string) => {
+ props = { ...props, color }
+ return driver
+ },
+ disabled: (disabled: boolean) => {
+ props = { ...props, disabled }
+ return driver
+ },
+ isLoading: (isLoading: boolean) => {
+ props = { ...props, isLoading }
+ return driver
+ },
+ onClick: (onClick: () => null) => {
+ props = { ...props, onClick }
+ return driver
+ },
+ size: (size: ButtonSize) => {
+ props = { ...props, size }
+ return driver
+ },
+ className: (className?: string) => {
+ props = { ...props, className }
+ return driver
+ },
+ variant: (variant: ButtonVariant) => {
+ props = { ...props, variant }
+ return driver
+ },
+ htmlType: (htmlType?: ButtonHtmlType) => {
+ props = { ...props, htmlType }
+ return driver
+ },
+ },
+ when: {
+ created: () => {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ render()
+
+ return driver
+ },
+ },
+ then: {
+ isExistButton: () => !!screen.getByTestId(ButtonDataHooks.Button),
+ isLoadding: () => !!screen.queryByText('Loading...'),
+ isExistChildren: () => !!screen.queryByText('children'),
+ getButton: () => screen.getByTestId(ButtonDataHooks.Button),
+ clickButton: () =>
+ fireEvent.click(screen.getByTestId(ButtonDataHooks.Button)),
+ },
+ }
+ return driver
+}
diff --git a/src/components/Button/Button.module.css b/src/components/Button/Button.module.css
new file mode 100644
index 0000000..2ae4e21
--- /dev/null
+++ b/src/components/Button/Button.module.css
@@ -0,0 +1,77 @@
+.default {
+ width: 206px;
+ padding: 15px 0;
+
+ font-weight: 500;
+ font-size: 14px;
+ /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
+ font-family: Roboto, sans-serif;
+ line-height: 16px;
+ letter-spacing: 0.3px;
+ text-transform: uppercase;
+
+ border-radius: 4px;
+}
+
+.primary {
+ color: var(--white-color);
+
+ background-color: var(--primary-color);
+ border: none;
+}
+
+.outline {
+ color: var(--primary-datk-color);
+
+ background-color: var(--white-color);
+ border: 2px solid var(--primary-color);
+}
+
+.outline:hover {
+ color: var(--primary-color);
+}
+
+.outline:active {
+ color: var(--secondary-dark-color);
+
+ border-color: var(--outline-color);
+}
+
+.outline:disabled {
+ color: var(--secondary-dark-color);
+
+ border-color: var(--outline-color);
+}
+
+.outline:disabled:hover {
+ color: var(--primary-color);
+}
+
+.primary:hover {
+ background-color: #ffb200;
+}
+
+.withCustomColor:hover {
+ background-color: unset;
+}
+
+.primary:active {
+ color: var(--secondary-dark-color);
+
+ background-color: var(--background-color) !important;
+}
+
+.primary:disabled {
+ color: var(--secondary-dark-color);
+
+ background-color: var(--background-color);
+}
+
+.primary:disabled:hover {
+ color: var(--primary-color);
+}
+
+.small {
+ width: 84px;
+ padding: 10px 0;
+}
diff --git a/src/components/Button/Button.spec.tsx b/src/components/Button/Button.spec.tsx
new file mode 100644
index 0000000..1929196
--- /dev/null
+++ b/src/components/Button/Button.spec.tsx
@@ -0,0 +1,85 @@
+import React from 'react'
+
+import { ButtonVariant } from 'components/Button/constants'
+
+import { createButtonDriver, DriverType } from './Button.driver'
+import styles from './Button.module.css'
+
+describe('Button', () => {
+ let driver: DriverType
+
+ beforeEach(() => {
+ driver = createButtonDriver()
+ })
+
+ describe('default button behavior', () => {
+ it('should display children', () => {
+ const isExistChildren = driver.given
+ .children(children)
+ .when.created()
+ .then.isExistChildren()
+ expect(isExistChildren).toBeTruthy()
+ })
+
+ it('should display default button', () => {
+ const isButtonExist = driver.when.created().then.isExistButton()
+ expect(isButtonExist).toBeTruthy()
+ })
+
+ it('should be disabled', () => {
+ const button = driver.given
+ .disabled(true)
+ .when.created()
+ .then.getButton()
+ expect(button).toHaveAttribute('disabled')
+ })
+
+ it('should be disabled when loading, ', () => {
+ const loadingButton = driver.given
+ .isLoading(true)
+ .when.created()
+ .then.getButton()
+ expect(loadingButton).toHaveAttribute('disabled')
+ })
+
+ it('should show loading status when loading', () => {
+ const loadingButton = driver.given
+ .isLoading(true)
+ .when.created()
+ .then.isLoadding()
+ expect(loadingButton).toBeTruthy()
+ })
+
+ it('should raise handler onClick func one time', () => {
+ const onClick = jest.fn()
+ driver.given.onClick(onClick).when.created().then.clickButton()
+ expect(onClick).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('layout button behavior', () => {
+ it('primary', () => {
+ const buttonPrimary = driver.given
+ .variant(ButtonVariant.primary)
+ .when.created()
+ .then.getButton()
+ expect(buttonPrimary).toHaveClass(styles.primary)
+ })
+
+ it('outline', () => {
+ const buttonPrimary = driver.given
+ .variant(ButtonVariant.outline)
+ .when.created()
+ .then.getButton()
+ expect(buttonPrimary).toHaveClass(styles.outline)
+ })
+
+ it('with custom ccolor', () => {
+ const buttonPrimary = driver.given
+ .color('#000')
+ .when.created()
+ .then.getButton()
+ expect(buttonPrimary).toHaveClass(styles.withCustomColor)
+ })
+ })
+})
diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx
new file mode 100644
index 0000000..0bc24c7
--- /dev/null
+++ b/src/components/Button/Button.stories.tsx
@@ -0,0 +1,66 @@
+import React from 'react'
+
+import { ComponentStory, ComponentMeta } from '@storybook/react'
+
+import { Button, PropsType } from './Button.component'
+import { ButtonSize, ButtonVariant } from './constants'
+
+export default {
+ children: Title,
+ disabled: false,
+ isLoading: false,
+ onClick: () => null,
+ size: ButtonSize.medium,
+ variant: ButtonVariant.primary,
+ color: '',
+} as ComponentMeta
+
+const Template: ComponentStory = (args: PropsType) => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+)
+
+export const SmallButton = Template.bind({})
+
+SmallButton.args = {
+ size: ButtonSize.small,
+ variant: ButtonVariant.primary,
+ disabled: false,
+ isLoading: false,
+ onClick: () => null,
+ children: Title,
+}
+
+export const MediumButton = Template.bind({})
+
+MediumButton.args = {
+ size: ButtonSize.medium,
+ variant: ButtonVariant.primary,
+ disabled: false,
+ isLoading: false,
+ onClick: () => null,
+ children: Title,
+}
+
+export const OutlineButton = Template.bind({})
+
+OutlineButton.args = {
+ size: ButtonSize.medium,
+ variant: ButtonVariant.outline,
+ disabled: false,
+ isLoading: false,
+ onClick: () => null,
+ children: Title,
+}
+
+export const CustomColot = Template.bind({})
+
+CustomColot.args = {
+ size: ButtonSize.medium,
+ variant: ButtonVariant.primary,
+ disabled: false,
+ isLoading: false,
+ onClick: () => null,
+ children: Title,
+ color: '#6DD230',
+}
diff --git a/src/components/Button/constants.ts b/src/components/Button/constants.ts
new file mode 100644
index 0000000..cbbe7c9
--- /dev/null
+++ b/src/components/Button/constants.ts
@@ -0,0 +1,17 @@
+export enum ButtonVariant {
+ outline = 'outline',
+ outlineWithIcon = 'primaryWithIcon',
+ primary = 'primary',
+ primaryWithIcon = 'primaryWithIcon',
+}
+
+export enum ButtonSize {
+ medium = 'medium',
+ small = 'small',
+}
+
+export type ButtonHtmlType = JSX.IntrinsicElements['button']['type']
+
+export enum DataHooks {
+ Button = 'button',
+}
diff --git a/src/components/Button/index.ts b/src/components/Button/index.ts
new file mode 100644
index 0000000..336ce12
--- /dev/null
+++ b/src/components/Button/index.ts
@@ -0,0 +1 @@
+export {}
diff --git a/src/shared/variables.css b/src/shared/variables.css
new file mode 100644
index 0000000..c80725b
--- /dev/null
+++ b/src/shared/variables.css
@@ -0,0 +1,15 @@
+:root {
+ --primary-color: #ffab2b;
+ --primary-datk-color: #252631;
+ --secondary-dark-color: #778ca2;
+ --thrid-dark-color: #98a9bc;
+ --blueberry-color: #4d7cfe;
+ --green-color: #6dd230;
+ --pink-color: #fe4d97;
+ --light-blue-color: #2ce5f6;
+ --outline-color: #e8ecef;
+ --background-color: #f2f4f6;
+ --light-background-color: #f8fafb;
+ --white-color: #fff;
+ --black-color: #000;
+}
diff --git a/src/stories/Button.stories.tsx b/src/stories/Button/Button.stories.tsx
similarity index 100%
rename from src/stories/Button.stories.tsx
rename to src/stories/Button/Button.stories.tsx
diff --git a/src/stories/Button.tsx b/src/stories/Button/Button.tsx
similarity index 97%
rename from src/stories/Button.tsx
rename to src/stories/Button/Button.tsx
index 73ba572..ac44ba6 100644
--- a/src/stories/Button.tsx
+++ b/src/stories/Button/Button.tsx
@@ -52,3 +52,5 @@ export const Button: FC = ({
)
}
+
+export default Button
diff --git a/src/stories/button.css b/src/stories/Button/button.css
similarity index 100%
rename from src/stories/button.css
rename to src/stories/Button/button.css
diff --git a/src/stories/Button/index.ts b/src/stories/Button/index.ts
new file mode 100644
index 0000000..8486fd6
--- /dev/null
+++ b/src/stories/Button/index.ts
@@ -0,0 +1 @@
+export * from './Button'
diff --git a/src/stories/Page.tsx b/src/stories/Page.tsx
index bbc91bf..499e1ab 100644
--- a/src/stories/Page.tsx
+++ b/src/stories/Page.tsx
@@ -7,7 +7,7 @@ type User = {
name: string
}
-export const Page: React.VFC = () => {
+export const Page: React.FC = () => {
const [user, setUser] = React.useState()
return (