diff --git a/__tests__/Button.test.tsx b/__tests__/Button.test.tsx
new file mode 100644
index 0000000..3294529
--- /dev/null
+++ b/__tests__/Button.test.tsx
@@ -0,0 +1,83 @@
+import { shallow } from 'enzyme';
+import React from 'react';
+import Button from '@/components/Button';
+import { Variant } from '@/components';
+
+describe('Button test', () => {
+ it('should render button', () => {
+ const container = shallow(
+
+ );
+
+ expect(container.find('button').length).toBe(1);
+ });
+
+ it('should render button as link', () => {
+ const container = shallow(
+
+ );
+
+ expect(container.find('a').length).toBe(1);
+ });
+
+ it('should render block button', () => {
+ const container = shallow(
+
+ );
+
+ expect(container.find('button').hasClass('btn-block')).toBeTruthy();
+ });
+
+ it('should render small button', () => {
+ const container = shallow(
+
+ );
+
+ expect(container.find('button').hasClass('btn-small')).toBeTruthy();
+ });
+
+ it('should render button with icon left', () => {
+ const container = shallow(
+
+ );
+
+ expect(container.find('button').find('i').text()).toContain('icon');
+ });
+
+ it('should render button with icon left', () => {
+ const container = shallow(
+
+ );
+
+ expect(container.find('button').find('i').text()).toContain('icon');
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index 4574ecf..a0b0bf3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1289,6 +1289,40 @@
}
}
},
+ "@fortawesome/fontawesome-common-types": {
+ "version": "0.2.32",
+ "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/0.2.32/fontawesome-common-types-0.2.32.tgz",
+ "integrity": "sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w=="
+ },
+ "@fortawesome/fontawesome-free": {
+ "version": "5.15.1",
+ "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-free/-/5.15.1/fontawesome-free-5.15.1.tgz",
+ "integrity": "sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ=="
+ },
+ "@fortawesome/fontawesome-svg-core": {
+ "version": "1.2.32",
+ "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/1.2.32/fontawesome-svg-core-1.2.32.tgz",
+ "integrity": "sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.32"
+ }
+ },
+ "@fortawesome/free-regular-svg-icons": {
+ "version": "5.15.1",
+ "resolved": "https://npm.fontawesome.com/@fortawesome/free-regular-svg-icons/-/5.15.1/free-regular-svg-icons-5.15.1.tgz",
+ "integrity": "sha512-eD9NWFy89e7SVVtrLedJUxIpCBGhd4x7s7dhesokjyo1Tw62daqN5UcuAGu1NrepLLq1IeAYUVfWwnOjZ/j3HA==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.32"
+ }
+ },
+ "@fortawesome/react-fontawesome": {
+ "version": "0.1.11",
+ "resolved": "https://npm.fontawesome.com/@fortawesome/react-fontawesome/-/0.1.11/react-fontawesome-0.1.11.tgz",
+ "integrity": "sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg==",
+ "requires": {
+ "prop-types": "^15.7.2"
+ }
+ },
"@hapi/address": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@@ -13798,7 +13832,6 @@
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
- "dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -13808,8 +13841,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
diff --git a/package.json b/package.json
index fc2f54d..286380b 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,10 @@
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
+ "@fortawesome/fontawesome-free": "^5.15.1",
+ "@fortawesome/fontawesome-svg-core": "^1.2.32",
+ "@fortawesome/free-regular-svg-icons": "^5.15.1",
+ "@fortawesome/react-fontawesome": "^0.1.11",
"clsx": "^1.1.1",
"react": "^17.0.1"
}
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
new file mode 100644
index 0000000..636bea7
--- /dev/null
+++ b/src/components/Button/index.tsx
@@ -0,0 +1,70 @@
+import * as React from 'react';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+import { Variant } from '@/components';
+
+export type ButtonComponentTypes = 'button' | 'a';
+
+export interface ButtonProps extends React.HTMLAttributes {
+ as?: ButtonComponentTypes;
+ variant: Variant | string;
+ iconLeft?: React.ReactElement;
+ iconRight?: React.ReactElement;
+ block?: boolean;
+ small?: boolean;
+}
+
+const Button = React.forwardRef((
+ {
+ as: Component = 'button',
+ children,
+ className,
+ variant,
+ iconLeft,
+ iconRight,
+ block,
+ small,
+ ...rest
+ },
+ ref
+): React.ReactElement => {
+ return (
+ }
+ className={clsx(
+ 'btn',
+ `btn-${variant}`,
+ block &&'btn-block',
+ small && 'btn-small',
+ className
+ )}
+ {...rest}
+ >
+ {iconLeft && (
+
+ {iconLeft}
+
+ )}
+ {children}
+ {iconRight && (
+
+ {iconRight}
+
+ )}
+
+ );
+});
+
+Button.displayName = 'Button';
+Button.propTypes = {
+ as: PropTypes.oneOf(['button', 'a']),
+ children: PropTypes.node.isRequired,
+ className: PropTypes.string,
+ variant: PropTypes.string.isRequired,
+ iconLeft: PropTypes.element,
+ iconRight: PropTypes.element,
+ block: PropTypes.bool,
+ small: PropTypes.bool
+};
+
+export default Button;
diff --git a/src/components/utils/Variant.ts b/src/components/utils/Variant.ts
index f4a6f3c..e1dc5dc 100644
--- a/src/components/utils/Variant.ts
+++ b/src/components/utils/Variant.ts
@@ -1,2 +1,15 @@
export enum Variant {
+ PRIMARY = 'primary',
+ PRIMARY_GHOST = 'primary-ghost',
+ SECONDARY = 'secondary',
+ DANGER = 'danger',
+ DANGER_GHOST = 'danger-ghost'
}
+
+export const variantList: string[] = [
+ 'primary',
+ 'primary-ghost',
+ 'secondary',
+ 'danger',
+ 'danger-ghost'
+];
diff --git a/src/style/base/_base.scss b/src/style/base/_base.scss
index 8342c14..90a9def 100644
--- a/src/style/base/_base.scss
+++ b/src/style/base/_base.scss
@@ -1,5 +1,7 @@
html {
font-size: var(--base-font-size);
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
}
body {
@@ -9,4 +11,5 @@ body {
font: {
family: var(--base-font-family);
}
+ line-height: 1.2rem;
}
diff --git a/src/style/base/_typography.scss b/src/style/base/_typography.scss
index a0867eb..aaaa460 100644
--- a/src/style/base/_typography.scss
+++ b/src/style/base/_typography.scss
@@ -82,6 +82,8 @@
url('../fonts/inter/inter-v2-latin-900.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
+
h1 {
font-size: 2rem;
color: mixins.color('gray', 900);
diff --git a/src/style/base/_variables.scss b/src/style/base/_variables.scss
index 3a041c0..580ece2 100644
--- a/src/style/base/_variables.scss
+++ b/src/style/base/_variables.scss
@@ -1,8 +1,6 @@
@use "mixins";
@use "colors";
-$primary-radius: 4px;
-
/**
* 1. Base
*/
@@ -12,6 +10,8 @@ $base-font-color: mixins.color('blueGray', 900);
$base-font-family: 'Inter, apple-sf-pro-text, Helvetica, Arial, sans-serif';
$base-font-size: 16px;
$base-border-color: mixins.color('gray', 300);
+$base-border-radius: 4px;
+$base-gutter: 1rem;
:root {
--base-font-size: #{$base-font-size};
@@ -20,10 +20,41 @@ $base-border-color: mixins.color('gray', 300);
--base-font-color: #{$base-font-color};
--base-font-family: #{$base-font-family};
--base-border-color: #{$base-border-color};
+ --base-border-radius: #{$base-border-radius};
+ --base-gutter: #{$base-gutter};
}
/**
- * 2. Panel
+ * 2. Colors
+ */
+$primary-color: mixins.color('deepPurple', 600);
+$primary-color-hover: mixins.color('deepPurple', 700);
+$primary-color-active: mixins.color('deepPurple', 800);
+
+$secondary-color: mixins.color('red', 600);
+$secondary-color-hover: mixins.color('red', 700);
+$secondary-color-active: mixins.color('red', 800);
+
+$danger-color: mixins.color('red', 700);
+$danger-color-hover: mixins.color('red', 800);
+$danger-color-active: mixins.color('red', 900);
+
+:root {
+ --primary-color: #{$primary-color};
+ --primary-color-hover: #{$primary-color-hover};
+ --primary-color-active: #{$primary-color-active};
+
+ --secondary-color: #{$secondary-color};
+ --secondary-color-hover: #{$secondary-color-hover};
+ --secondary-color-active: #{$secondary-color-active};
+
+ --danger-color: #{$danger-color};
+ --danger-color-hover: #{$danger-color-hover};
+ --danger-color-active: #{$danger-color-active};
+}
+
+/**
+ * 3. Panel
*/
$panel-background: #fff;
$panel-gutter: 2rem;
@@ -34,10 +65,61 @@ $panel-gutter: 2rem;
}
/**
- * 3. Page
+ * 4. Page
*/
$page-gutter: 2rem;
:root {
--page-gutter: #{$page-gutter};
}
+
+/**
+ * 5. Button
+ */
+$button-padding: 1rem;
+$button-font-weight: 600;
+$button-font-size: 1rem;
+
+$primary-button-ghost-hover-background: mixins.color('deepPurple', 50);
+$primary-button-ghost-active-background: mixins.color('deepPurple', 75);
+
+$secondary-button-background: transparent;
+$secondary-button-hover-background: mixins.color('deepPurple', 50);
+$secondary-button-active-background: mixins.color('deepPurple', 75);
+
+$danger-button-ghost-hover-background: mixins.color('red', 50);
+$danger-button-ghost-active-background: mixins.color('red', 75);
+
+:root {
+ --button-padding: #{$button-padding};
+ --button-font-weight: #{$button-font-weight};
+ --button-font-size: #{$button-font-size};
+
+ --primary-button-background: var(--primary-color);
+ --primary-button-border: var(--primary-color);
+ --primary-button-hover-background: var(--primary-color-hover);
+ --primary-button-hover-border: var(--primary-color-hover);
+ --primary-button-active-background: var(--primary-color-active);
+ --primary-button-active-border: var(--primary-color-active);
+
+ --primary-button-ghost-color: var(--primary-color);
+ --primary-button-ghost-border: var(--primary-color);
+ --primary-button-ghost-hover-background: #{$primary-button-ghost-hover-background};
+ --primary-button-ghost-active-background: #{$primary-button-ghost-active-background};
+
+ --secondary-button-background: #{$secondary-button-background};
+ --secondary-button-hover-background: #{$secondary-button-hover-background};
+ --secondary-button-active-background: #{$secondary-button-active-background};
+
+ --danger-button-background: var(--danger-color);
+ --danger-button-border: var(--danger-color);
+ --danger-button-hover-background: var(--danger-color-hover);
+ --danger-button-hover-border: var(--danger-color-hover);
+ --danger-button-active-background: var(--danger-color-active);
+ --danger-button-active-border: var(--danger-color-active);
+
+ --danger-button-ghost-color: var(--danger-color);
+ --danger-button-ghost-border: var(--danger-color);
+ --danger-button-ghost-hover-background: #{$danger-button-ghost-hover-background};
+ --danger-button-ghost-active-background: #{$danger-button-ghost-active-background};
+}
diff --git a/src/style/components/_button.scss b/src/style/components/_button.scss
new file mode 100644
index 0000000..324efe1
--- /dev/null
+++ b/src/style/components/_button.scss
@@ -0,0 +1,107 @@
+.btn {
+ border: solid 1px transparent;
+ border-radius: var(--base-border-radius);
+ cursor: pointer;
+ display: inline-flex;
+ padding: var(--button-padding);
+ outline: none;
+ justify-content: center;
+ font: {
+ weight: var(--button-font-weight);
+ size: var(--button-font-size);
+ }
+
+ &.btn-primary {
+ background: var(--primary-button-background);
+ border-color: var(--primary-button-border);
+ color: #fff;
+
+ &:hover {
+ background: var(--primary-button-hover-background);
+ border-color: var(--primary-button-hover-border);
+ }
+
+ &:active {
+ background: var(--primary-button-active-background);
+ border-color: var(--primary-button-active-border);
+ }
+ }
+
+ &.btn-primary-ghost {
+ border-color: var(--primary-button-ghost-border);
+ color: var(--primary-button-ghost-color);
+
+ &:hover {
+ background: var(--primary-button-ghost-hover-background);
+ border-color: var(--primary-button-border);
+ }
+
+ &:active {
+ background: var(--primary-button-ghost-active-background);
+ border-color: var(--primary-button-border);
+ }
+ }
+
+ &.btn-secondary {
+ background: transparent;
+ color: var(--primary-color);
+ border-color: var(--base-border-color);
+
+ &:hover {
+ background: var(--secondary-button-hover-background);
+ }
+
+ &:active {
+ background: var(--secondary-button-active-background);
+ }
+ }
+
+ &.btn-danger {
+ background: var(--danger-button-background);
+ border-color: var(--danger-button-border);
+ color: #fff;
+
+ &:hover {
+ background: var(--danger-button-hover-background);
+ border-color: var(--danger-button-hover-border);
+ }
+
+ &:active {
+ background: var(--danger-button-active-background);
+ border-color: var(--danger-button-active-border);
+ }
+ }
+
+ &.btn-danger-ghost {
+ border-color: var(--danger-button-ghost-border);
+ color: var(--danger-button-ghost-color);
+
+ &:hover {
+ background: var(--danger-button-ghost-hover-background);
+ border-color: var(--danger-button-ghost-border);
+ }
+
+ &:active {
+ background: var(--danger-button-ghost-active-background);
+ border-color: var(--danger-button-ghost-border);
+ }
+ }
+
+ .icon-container {
+ &.icon-left {
+ margin-right: var(--base-gutter);
+ }
+
+ &.icon-right {
+ margin-left: var(--base-gutter);
+ }
+ }
+
+ &.btn-block {
+ width: 100%;
+ }
+
+ &.btn-small {
+ padding: 0.5rem;
+ }
+}
diff --git a/src/style/index.scss b/src/style/index.scss
index c804147..6a062b6 100644
--- a/src/style/index.scss
+++ b/src/style/index.scss
@@ -10,5 +10,6 @@
/**
* 2. Components
*/
+@use "components/button";
@use "components/page";
@use "components/panel";