diff --git a/__tests__/FieldBase.test.tsx b/__tests__/FieldBase.test.tsx
new file mode 100644
index 0000000..4000909
--- /dev/null
+++ b/__tests__/FieldBase.test.tsx
@@ -0,0 +1,117 @@
+import React from 'react';
+import { mount, render } from 'enzyme';
+import FieldBase from '@/components/Field/FieldBase';
+import { Variant } from '@/components';
+import { FieldContext } from '@/components/Field/FieldContext';
+
+describe('FieldBase test', () => {
+ it('should render field base', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-base').length).toBe(1);
+ });
+
+ it('should set variant', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-base.form-field-primary').length).toBe(1);
+ });
+
+ it('should set variant', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-base.form-field-primary').length).toBe(1);
+ });
+
+ it('should make floating label component', () => {
+ const container = render(
+
+ test}
+ />
+
+ );
+
+ expect(container.find('.form-field-base').hasClass('floating-label')).toBeTruthy();
+ expect(container.find('.form-field-base .form-field-label-floating strong').text())
+ .toBe('test');
+ });
+
+ it('should make invalid state icon', () => {
+ const container = render(
+
+ test}
+ valid={false}
+ >
+
+ {({ stateIcon }) => stateIcon}
+
+
+
+ );
+
+ expect(container.find('.field-state-icon .icon-close-circle').length).toBe(1);
+ })
+
+ it('should make valid state icon', () => {
+ const container = render(
+
+ test}
+ valid={true}
+ >
+
+ {({ stateIcon }) => stateIcon}
+
+
+
+ );
+
+ expect(container.find('.field-state-icon .icon-checkmark-circle-2').length).toBe(1);
+ })
+
+ it('should change focus and value', () => {
+ const container = mount(
+
+
+
+ {({ changeFocus, changeValue }) => {
+ changeFocus(true);
+ changeValue(true);
+ return undefined
+ }}
+
+
+
+ );
+
+ expect(container.find('.form-field-base.focused.has-value').length).toBe(1);
+ })
+
+ it('should render actions', () => {
+ const container = mount(
+
+ foo} />
+
+ );
+
+ expect(container.find('.form-field-base .form-field-actions span').text()).toBe('foo');
+ });
+});
diff --git a/__tests__/FieldContainer.test.tsx b/__tests__/FieldContainer.test.tsx
new file mode 100644
index 0000000..d7b6975
--- /dev/null
+++ b/__tests__/FieldContainer.test.tsx
@@ -0,0 +1,25 @@
+import { render } from 'enzyme';
+import React from 'react';
+import FieldContainer from '@/components/Field/FieldContainer';
+
+describe('FieldBase test', () => {
+ it('should render field container', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-container').length).toBe(1);
+ });
+
+ it('should render field container toggle', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-container.toggles').length).toBe(1);
+ });
+});
diff --git a/__tests__/FieldContext.test.tsx b/__tests__/FieldContext.test.tsx
new file mode 100644
index 0000000..37ff5e4
--- /dev/null
+++ b/__tests__/FieldContext.test.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { render } from 'enzyme';
+import { FieldContext } from '@/components/Field/FieldContext';
+
+describe('FieldContext test', () => {
+ it('should use default values when no consumer used', () => {
+ const mockChangeValue = jest.fn();
+ const mockChangeFocus = jest.fn();
+ const container = render(
+
+
+ {({ changeValue, changeFocus }) => {
+ const a = changeValue(true);
+ const b = changeFocus(true);
+
+ return {a === undefined && b === undefined ? 'undefined' : 'defined'};
+ }}
+
+
,
+ { context: {
+ changeValue: mockChangeValue,
+ changeFocus: mockChangeFocus
+ } }
+ )
+
+ expect(container.find('span').text()).toBe('undefined');
+ });
+})
diff --git a/__tests__/Form.test.tsx b/__tests__/Form.test.tsx
new file mode 100644
index 0000000..9a3f03f
--- /dev/null
+++ b/__tests__/Form.test.tsx
@@ -0,0 +1,60 @@
+import { render } from 'enzyme';
+import React from 'react';
+import FormGroup from '@/components/Form/Group';
+import FormLabel from '@/components/Form/Label';
+import FormMessage from '@/components/Form/Message';
+
+describe('Form test', () => {
+ it('should render form-group', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-group').length).toBe(1);
+ });
+
+ it('should render form label', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-label').length).toBe(1);
+ });
+
+ it('should render form message', () => {
+ const container = render(
+
+ Message
+
+ );
+
+ expect(container.find('.form-message').length).toBe(1);
+ expect((container.find('.form-message').text())).toBe('Message');
+ });
+
+ it('should render valid form message', () => {
+ const container = render(
+
+ Message
+
+ );
+
+ expect(container.find('.form-message.form-message-valid').length).toBe(1);
+ expect((container.find('.form-message').text())).toBe('Message');
+ });
+
+ it('should render invalid form message', () => {
+ const container = render(
+
+ Message
+
+ );
+
+ expect(container.find('.form-message.form-message-invalid').length).toBe(1);
+ expect((container.find('.form-message').text())).toBe('Message');
+ });
+});
diff --git a/__tests__/Selectfield.test.tsx b/__tests__/Selectfield.test.tsx
new file mode 100644
index 0000000..435b28a
--- /dev/null
+++ b/__tests__/Selectfield.test.tsx
@@ -0,0 +1,112 @@
+import { mount, render } from 'enzyme';
+import React from 'react';
+import { SelectField } from '@/components';
+
+describe('SelectField test', () => {
+ it('should render text input', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-base select').length).toBe(1);
+ });
+
+ it('Should set initial value with value prop', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+ fn()}>
+
+
+
+ );
+
+ expect(container.find('select').props().value).toBe('foo');
+ });
+
+ it('Should set initial value with defaultValue prop', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+ fn()}>
+
+
+
+ );
+
+ expect(container.find('select').props().value).toBe('foo');
+ });
+
+ it('Should set initial value based on selected option', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+ fn()}>
+
+
+
+ );
+
+ expect(container.find('select').props().value).toBe('foo');
+ });
+
+ it('should give focus class when focused', () => {
+ const container = mount(
+
+ );
+
+ container.find('select').simulate('focus');
+
+ expect(container.find('.form-field-base').hasClass('focused')).toBeTruthy();
+ });
+
+ it('should not have focus class when blurred after focus', () => {
+ const container = mount(
+
+ );
+
+ container.find('select').simulate('focus').simulate('blur');
+
+ expect(container.find('.form-field-base').hasClass('focused')).toBeFalsy();
+ });
+
+ it('should call onChange method when changing', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+ fn()}/>
+ );
+
+ container.find('select').simulate('change');
+
+ expect(fn).toHaveBeenCalled();
+ });
+
+ it('should not call onChange method when ommited', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+
+ );
+
+ container.find('select').simulate('change');
+
+ expect(fn).not.toHaveBeenCalled();
+ });
+
+ it('should ignore invalid react elements and select last selected option', () => {
+
+ const container = mount(
+
+ {undefined}
+
+
+
+
+ );
+
+ expect(container.find('select').props().value).toBe('bar');
+ });
+});
diff --git a/__tests__/Textfield.test.tsx b/__tests__/Textfield.test.tsx
new file mode 100644
index 0000000..18dd492
--- /dev/null
+++ b/__tests__/Textfield.test.tsx
@@ -0,0 +1,130 @@
+import { mount, render } from 'enzyme';
+import React from 'react';
+import { Button, TextField, Variant } from '@/components';
+
+describe('TextField test', () => {
+ it('should render text input', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-base input').length).toBe(1);
+ });
+
+ it('should render variant class', () => {
+ const container = render(
+
+
+
+ );
+
+ expect(container.find('.form-field-base').hasClass('form-field-primary')).toBeTruthy();
+ });
+
+ it('should give focus class when focused', () => {
+ const container = mount(
+
+ );
+
+ container.find('input').simulate('focus');
+
+ expect(container.find('.form-field-base').hasClass('focused')).toBeTruthy();
+ });
+
+ it('should not have focus class when blurred after focus', () => {
+ const container = mount(
+
+ );
+
+ container.find('input').simulate('focus').simulate('blur');
+
+ expect(container.find('.form-field-base').hasClass('focused')).toBeFalsy();
+ });
+
+ it('should call onChange method when changing', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+ fn()}/>
+ );
+
+ container.find('input').simulate('change');
+
+ expect(fn).toHaveBeenCalled();
+ });
+
+ it('should not call onChange method when ommited', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+
+ );
+
+ container.find('input').simulate('change');
+
+ expect(fn).not.toHaveBeenCalled();
+ });
+
+ it('Should be controllable from outside', () => {
+ const fn = jest.fn();
+
+ const container = mount(
+ fn()}/>
+ );
+
+ expect(container.find('input').prop('value')).toBe('Test');
+
+ container.find('input').simulate('change', {target: {value: 'Foo'}});
+
+ expect(container.find('input').prop('value')).toBe('Foo');
+ expect(fn).toHaveBeenCalled();
+ });
+
+ it('should render floating label', () => {
+ const container = mount(
+
+
+
+ );
+
+
+ expect(container.find('input').hasClass('floating-label')).toBeFalsy();
+ expect(container.find('.form-field-base .form-field-label-floating').length).toBe(1);
+ });
+
+ it('should render valid fields', () => {
+ const container = mount(
+
+
+
+ );
+
+
+ expect(container.find('.form-field-base').hasClass('form-field-valid')).toBeTruthy();
+ });
+
+ it('should render invalid fields', () => {
+ const container = mount(
+
+
+
+ );
+
+
+ expect(container.find('.form-field-base').hasClass('form-field-invalid')).toBeTruthy();
+ });
+
+ it('should render field actions', () => {
+ const container = mount(
+
+ ]}/>
+
+ );
+
+ expect(container.find('.form-field-base .form-field-actions > *').length).toBe(1);
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index 4574ecf..91cf2dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1261,9 +1261,9 @@
"dev": true
},
"@eslint/eslintrc": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.0.tgz",
- "integrity": "sha512-+cIGPCBdLCzqxdtwppswP+zTsH9BOIGzAeKfBIbtb4gW/giMlfMwP0HUSFfhzh20f9u8uZ8hOp62+4GPquTbwQ==",
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz",
+ "integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
@@ -2259,9 +2259,9 @@
}
},
"@types/enzyme": {
- "version": "3.10.7",
- "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.7.tgz",
- "integrity": "sha512-J+0wduPGAkzOvW7sr6hshGv1gBI3WXLRTczkRKzVPxLP3xAkYxZmvvagSBPw8Z452fZ8TGUxCmAXcb44yLQksw==",
+ "version": "3.10.8",
+ "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.8.tgz",
+ "integrity": "sha512-vlOuzqsTHxog6PV79+tvOHFb6hq4QZKMq1lLD9MaWD1oec2lHTKndn76XOpSwCA0oFTaIbKVPrgM3k78Jjd16g==",
"dev": true,
"requires": {
"@types/cheerio": "*",
@@ -2407,9 +2407,9 @@
"dev": true
},
"@types/react": {
- "version": "16.9.53",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.53.tgz",
- "integrity": "sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==",
+ "version": "16.9.55",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.55.tgz",
+ "integrity": "sha512-6KLe6lkILeRwyyy7yG9rULKJ0sXplUsl98MGoCfpteXf9sPWFWWMknDcsvubcpaTdBuxtsLF6HDUwdApZL/xIg==",
"dev": true,
"requires": {
"@types/prop-types": "*",
@@ -2417,9 +2417,9 @@
}
},
"@types/react-dom": {
- "version": "16.9.8",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
- "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==",
+ "version": "16.9.9",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.9.tgz",
+ "integrity": "sha512-jE16FNWO3Logq/Lf+yvEAjKzhpST/Eac8EMd1i4dgZdMczfgqC8EjpxwNgEe3SExHYLliabXDh9DEhhqnlXJhg==",
"dev": true,
"requires": {
"@types/react": "*"
@@ -2535,13 +2535,13 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.5.0.tgz",
- "integrity": "sha512-mjb/gwNcmDKNt+6mb7Aj/TjKzIJjOPcoCJpjBQC9ZnTRnBt1p4q5dJSSmIqAtsZ/Pff5N+hJlbiPc5bl6QN4OQ==",
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.6.1.tgz",
+ "integrity": "sha512-SNZyflefTMK2JyrPfFFzzoy2asLmZvZJ6+/L5cIqg4HfKGiW2Gr1Go1OyEVqne/U4QwmoasuMwppoBHWBWF2nA==",
"dev": true,
"requires": {
- "@typescript-eslint/experimental-utils": "4.5.0",
- "@typescript-eslint/scope-manager": "4.5.0",
+ "@typescript-eslint/experimental-utils": "4.6.1",
+ "@typescript-eslint/scope-manager": "4.6.1",
"debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
@@ -2549,6 +2549,68 @@
"tsutils": "^3.17.1"
},
"dependencies": {
+ "@typescript-eslint/experimental-utils": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.1.tgz",
+ "integrity": "sha512-qyPqCFWlHZXkEBoV56UxHSoXW2qnTr4JrWVXOh3soBP3q0o7p4pUEMfInDwIa0dB/ypdtm7gLOS0hg0a73ijfg==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.3",
+ "@typescript-eslint/scope-manager": "4.6.1",
+ "@typescript-eslint/types": "4.6.1",
+ "@typescript-eslint/typescript-estree": "4.6.1",
+ "eslint-scope": "^5.0.0",
+ "eslint-utils": "^2.0.0"
+ }
+ },
+ "@typescript-eslint/scope-manager": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.6.1.tgz",
+ "integrity": "sha512-f95+80r6VdINYscJY1KDUEDcxZ3prAWHulL4qRDfNVD0I5QAVSGqFkwHERDoLYJJWmEAkUMdQVvx7/c2Hp+Bjg==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.6.1",
+ "@typescript-eslint/visitor-keys": "4.6.1"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.6.1.tgz",
+ "integrity": "sha512-k2ZCHhJ96YZyPIsykickez+OMHkz06xppVLfJ+DY90i532/Cx2Z+HiRMH8YZQo7a4zVd/TwNBuRCdXlGK4yo8w==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.1.tgz",
+ "integrity": "sha512-/J/kxiyjQQKqEr5kuKLNQ1Finpfb8gf/NpbwqFFYEBjxOsZ621r9AqwS9UDRA1Rrr/eneX/YsbPAIhU2rFLjXQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.6.1",
+ "@typescript-eslint/visitor-keys": "4.6.1",
+ "debug": "^4.1.1",
+ "globby": "^11.0.1",
+ "is-glob": "^4.0.1",
+ "lodash": "^4.17.15",
+ "semver": "^7.3.2",
+ "tsutils": "^3.17.1"
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.1.tgz",
+ "integrity": "sha512-owABze4toX7QXwOLT3/D5a8NecZEjEWU1srqxENTfqsY3bwVnl3YYbOh6s1rp2wQKO9RTHFGjKes08FgE7SVMw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.6.1",
+ "eslint-visitor-keys": "^2.0.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
+ "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
+ "dev": true
+ },
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
@@ -2572,15 +2634,71 @@
}
},
"@typescript-eslint/parser": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.5.0.tgz",
- "integrity": "sha512-xb+gmyhQcnDWe+5+xxaQk5iCw6KqXd8VQxGiTeELTMoYeRjpocZYYRP1gFVM2C8Yl0SpUvLa1lhprwqZ00w3Iw==",
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.6.1.tgz",
+ "integrity": "sha512-lScKRPt1wM9UwyKkGKyQDqf0bh6jm8DQ5iN37urRIXDm16GEv+HGEmum2Fc423xlk5NUOkOpfTnKZc/tqKZkDQ==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "4.5.0",
- "@typescript-eslint/types": "4.5.0",
- "@typescript-eslint/typescript-estree": "4.5.0",
+ "@typescript-eslint/scope-manager": "4.6.1",
+ "@typescript-eslint/types": "4.6.1",
+ "@typescript-eslint/typescript-estree": "4.6.1",
"debug": "^4.1.1"
+ },
+ "dependencies": {
+ "@typescript-eslint/scope-manager": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.6.1.tgz",
+ "integrity": "sha512-f95+80r6VdINYscJY1KDUEDcxZ3prAWHulL4qRDfNVD0I5QAVSGqFkwHERDoLYJJWmEAkUMdQVvx7/c2Hp+Bjg==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.6.1",
+ "@typescript-eslint/visitor-keys": "4.6.1"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.6.1.tgz",
+ "integrity": "sha512-k2ZCHhJ96YZyPIsykickez+OMHkz06xppVLfJ+DY90i532/Cx2Z+HiRMH8YZQo7a4zVd/TwNBuRCdXlGK4yo8w==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.1.tgz",
+ "integrity": "sha512-/J/kxiyjQQKqEr5kuKLNQ1Finpfb8gf/NpbwqFFYEBjxOsZ621r9AqwS9UDRA1Rrr/eneX/YsbPAIhU2rFLjXQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.6.1",
+ "@typescript-eslint/visitor-keys": "4.6.1",
+ "debug": "^4.1.1",
+ "globby": "^11.0.1",
+ "is-glob": "^4.0.1",
+ "lodash": "^4.17.15",
+ "semver": "^7.3.2",
+ "tsutils": "^3.17.1"
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.1.tgz",
+ "integrity": "sha512-owABze4toX7QXwOLT3/D5a8NecZEjEWU1srqxENTfqsY3bwVnl3YYbOh6s1rp2wQKO9RTHFGjKes08FgE7SVMw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "4.6.1",
+ "eslint-visitor-keys": "^2.0.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
+ "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
+ "dev": true
+ },
+ "semver": {
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+ "dev": true
+ }
}
},
"@typescript-eslint/scope-manager": {
@@ -6176,13 +6294,13 @@
}
},
"eslint": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.0.tgz",
- "integrity": "sha512-n5pEU27DRxCSlOhJ2rO57GDLcNsxO0LPpAbpFdh7xmcDmjmlGUfoyrsB3I7yYdQXO5N3gkSTiDrPSPNFiiirXA==",
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.1.tgz",
+ "integrity": "sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
- "@eslint/eslintrc": "^0.2.0",
+ "@eslint/eslintrc": "^0.2.1",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -13798,7 +13916,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 +13925,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=="
}
}
},
@@ -14018,12 +14134,13 @@
}
},
"react": {
- "version": "17.0.1",
- "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
- "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+ "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"requires": {
"loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2"
}
},
"react-app-polyfill": {
@@ -14104,26 +14221,15 @@
}
},
"react-dom": {
- "version": "17.0.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz",
- "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==",
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
+ "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
- "scheduler": "^0.20.1"
- },
- "dependencies": {
- "scheduler": {
- "version": "0.20.1",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz",
- "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==",
- "dev": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- }
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.19.1"
}
},
"react-error-overlay": {
@@ -14218,37 +14324,23 @@
}
}
},
- "react-shallow-renderer": {
- "version": "16.14.1",
- "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz",
- "integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==",
- "dev": true,
- "requires": {
- "object-assign": "^4.1.1",
- "react-is": "^16.12.0 || ^17.0.0"
- }
- },
"react-test-renderer": {
- "version": "17.0.1",
- "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.1.tgz",
- "integrity": "sha512-/dRae3mj6aObwkjCcxZPlxDFh73XZLgvwhhyON2haZGUEhiaY5EjfAdw+d/rQmlcFwdTpMXCSGVk374QbCTlrA==",
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz",
+ "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==",
"dev": true,
"requires": {
"object-assign": "^4.1.1",
- "react-is": "^17.0.1",
- "react-shallow-renderer": "^16.13.1",
- "scheduler": "^0.20.1"
+ "prop-types": "^15.6.2",
+ "react-is": "^16.8.6",
+ "scheduler": "^0.19.1"
},
"dependencies": {
- "scheduler": {
- "version": "0.20.1",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz",
- "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==",
- "dev": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
+ "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
}
}
},
@@ -16771,9 +16863,9 @@
"dev": true
},
"ts-loader": {
- "version": "8.0.6",
- "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.6.tgz",
- "integrity": "sha512-c8XkRbhKxFLbiIwZR7FBGWDq0MIz/QSpx3CGpj0abJxD5YVX8oDhQkJLeGbXUPRIlaX4Ajmr77fOiFVZ3gSU7g==",
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.7.tgz",
+ "integrity": "sha512-ooa4wxlZ9TOXaJ/iVyZlWsim79Ul4KyifSwyT2hOrbQA6NZJypsLOE198o8Ko+JV+ZHnMArvWcl4AnRqpCU/Mw==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
@@ -16783,54 +16875,11 @@
"semver": "^6.0.0"
},
"dependencies": {
- "braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
- "requires": {
- "fill-range": "^7.0.1"
- }
- },
- "fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
- "requires": {
- "to-regex-range": "^5.0.1"
- }
- },
- "is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true
- },
- "micromatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
- "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
- "dev": true,
- "requires": {
- "braces": "^3.0.1",
- "picomatch": "^2.0.5"
- }
- },
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
- },
- "to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "requires": {
- "is-number": "^7.0.0"
- }
}
}
},
@@ -16952,9 +17001,9 @@
}
},
"typescript": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz",
- "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
+ "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==",
"dev": true
},
"typical": {
diff --git a/package.json b/package.json
index fc2f54d..fdb176b 100644
--- a/package.json
+++ b/package.json
@@ -16,31 +16,31 @@
"@babel/preset-env": "^7.11.0",
"@babel/preset-react": "^7.10.4",
"@babel/preset-typescript": "^7.10.4",
- "@types/enzyme": "^3.10.5",
+ "@types/enzyme": "^3.10.8",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/jest": "^26.0.10",
- "@types/react": "^16.9.46",
- "@types/react-dom": "^16.9.8",
+ "@types/react": "^16.9.55",
+ "@types/react-dom": "^16.9.9",
"@types/react-is": "^16.7.1",
- "@typescript-eslint/eslint-plugin": "^4.5.0",
- "@typescript-eslint/parser": "^4.5.0",
+ "@typescript-eslint/eslint-plugin": "^4.6.1",
+ "@typescript-eslint/parser": "^4.6.1",
"babel-plugin-root-import": "^6.6.0",
"dart-sass": "^1.25.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
- "eslint": "^7.7.0",
+ "eslint": "^7.12.1",
"eslint-plugin-react": "^7.20.6",
"flush-promises": "^1.0.2",
- "react-dom": "^17.0.1",
+ "react-dom": "^16.14.0",
"react-scripts": "^4.0.0",
- "react-test-renderer": "^17.0.1",
- "ts-loader": "^8.0.2",
- "typescript": "^4.0.3",
+ "react-test-renderer": "^16.14.0",
+ "ts-loader": "^8.0.7",
+ "typescript": "^4.0.5",
"webpack-cli": "^4.1.0",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"clsx": "^1.1.1",
- "react": "^17.0.1"
+ "react": "^16.14.0"
}
}
diff --git a/src/components/Field/FieldBase.tsx b/src/components/Field/FieldBase.tsx
new file mode 100644
index 0000000..93e8e9b
--- /dev/null
+++ b/src/components/Field/FieldBase.tsx
@@ -0,0 +1,97 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { Icon, Variant } from '@/components';
+import clsx from 'clsx';
+import { ReactElement, useState } from 'react';
+import { FieldContext } from './FieldContext';
+
+interface FieldBaseProps {
+ actions?: React.ReactNode;
+ className?: string;
+ label?: React.ReactNode;
+ valid?: boolean;
+ variant?: Variant | string;
+ value?: string | ReadonlyArray | number;
+}
+
+export const getStateIcon = (valid: boolean | undefined): ReactElement | undefined => {
+ if (valid === undefined) {
+ return undefined;
+ }
+
+ return (
+
+
+
+ );
+}
+
+const FieldBaseProps = ({
+ actions,
+ className,
+ children,
+ label,
+ variant = Variant.PRIMARY,
+ valid
+}: React.PropsWithChildren,
+): React.ReactElement => {
+ const [hasValue, setHasValue] = useState(false);
+ const [hasFocus, setHasFocus] = useState(false);
+
+ return (
+
+ {label && (
+
+ )}
+
setHasValue(value),
+ changeFocus: (focus: boolean) => setHasFocus(focus),
+ stateIcon: getStateIcon(valid)
+ }}>
+ {children}
+
+ {actions && (
+
+ {actions}
+
+ )}
+
+ );
+};
+
+export const propTypes = {
+ actions: PropTypes.node,
+ children: PropTypes.node,
+ label: PropTypes.node,
+ onChange: PropTypes.func,
+ valid: PropTypes.bool,
+ value: PropTypes.string,
+ variant: PropTypes.string,
+}
+
+FieldBaseProps.displayName = 'FieldBase';
+FieldBaseProps.propTypes = propTypes;
+
+export default FieldBaseProps;
diff --git a/src/components/Field/FieldContainer.tsx b/src/components/Field/FieldContainer.tsx
new file mode 100644
index 0000000..d989d49
--- /dev/null
+++ b/src/components/Field/FieldContainer.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+
+interface FieldContainerProps extends React.HTMLAttributes {
+ toggles?: boolean;
+}
+
+const FieldContainer = React.forwardRef((
+ {
+ children,
+ className,
+ toggles
+ },
+ ref
+): React.ReactElement => (
+
+ {children}
+
+));
+
+FieldContainer.displayName = 'FieldContainer';
+FieldContainer.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ toggles: PropTypes.bool
+}
+
+export default FieldContainer;
diff --git a/src/components/Field/FieldContext.ts b/src/components/Field/FieldContext.ts
new file mode 100644
index 0000000..8115387
--- /dev/null
+++ b/src/components/Field/FieldContext.ts
@@ -0,0 +1,13 @@
+import React from 'react';
+
+export interface FieldContextProps {
+ changeFocus: (focus: boolean) => void;
+ changeValue: (value: boolean) => void;
+ stateIcon: React.ReactElement | undefined
+}
+
+export const FieldContext = React.createContext({
+ changeFocus: () => undefined,
+ changeValue: () => undefined,
+ stateIcon: undefined
+});
diff --git a/src/components/Form/Group.tsx b/src/components/Form/Group.tsx
new file mode 100644
index 0000000..1865bb4
--- /dev/null
+++ b/src/components/Form/Group.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+
+interface FormGroupProps extends React.HTMLAttributes {
+}
+
+const FormGroup = React.forwardRef((
+ {
+ children,
+ className,
+ ...rest
+ },
+ ref
+): React.ReactElement => (
+
+ {children}
+
+));
+
+FormGroup.displayName = 'FormGroup';
+FormGroup.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+}
+
+export default FormGroup;
diff --git a/src/components/Form/Label.tsx b/src/components/Form/Label.tsx
new file mode 100644
index 0000000..54d53eb
--- /dev/null
+++ b/src/components/Form/Label.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+
+type FormLabelProps = React.LabelHTMLAttributes;
+
+const FormLabel = React.forwardRef((
+ {
+ children,
+ className,
+ htmlFor,
+ ...rest
+ },
+ ref
+): React.ReactElement => (
+
+));
+
+FormLabel.displayName = 'FormLabel';
+FormLabel.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ htmlFor: PropTypes.string
+}
+
+export default FormLabel;
diff --git a/src/components/Form/Message.tsx b/src/components/Form/Message.tsx
new file mode 100644
index 0000000..f3f6473
--- /dev/null
+++ b/src/components/Form/Message.tsx
@@ -0,0 +1,37 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+
+interface FormMessageProps extends React.HTMLAttributes {
+ className?: string;
+ valid?: boolean;
+}
+
+const FormMessage = React.forwardRef((
+ {
+ children,
+ className,
+ valid
+ },
+ ref
+): React.ReactElement => (
+
+ {children}
+
+));
+
+FormMessage.displayName = 'FormMessage';
+FormMessage.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ valid: PropTypes.bool
+}
+
+export default FormMessage;
diff --git a/src/components/SelectField/SelectField.tsx b/src/components/SelectField/SelectField.tsx
new file mode 100644
index 0000000..53b874d
--- /dev/null
+++ b/src/components/SelectField/SelectField.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import FieldBaseProps from '@/components/Field/FieldBase';
+import FieldBase, { propTypes as basePropTypes } from '@/components/Field/FieldBase';
+import SelectFieldInput from '@/components/SelectField/SelectFieldInput';
+
+export type SelectFieldProps = React.SelectHTMLAttributes & FieldBaseProps;
+
+const SelectField = React.forwardRef((
+ {
+ actions,
+ label,
+ valid,
+ variant,
+ ...rest
+ },
+ ref
+): React.ReactElement => (
+
+
+
+));
+
+export const propTypes = basePropTypes;
+
+SelectField.displayName = 'SelectField';
+SelectField.propTypes = propTypes;
+
+export default SelectField;
diff --git a/src/components/SelectField/SelectFieldInput.tsx b/src/components/SelectField/SelectFieldInput.tsx
new file mode 100644
index 0000000..4b8d320
--- /dev/null
+++ b/src/components/SelectField/SelectFieldInput.tsx
@@ -0,0 +1,75 @@
+import * as React from 'react';
+import { propTypes, SelectFieldProps } from '@/components/SelectField/SelectField';
+import { Children, isValidElement, ReactNode, useContext, useEffect, useState } from 'react';
+import { FieldContext, FieldContextProps } from '@/components/Field/FieldContext';
+import clsx from 'clsx';
+import FieldContainer from '@/components/Field/FieldContainer';
+
+const determineInitialValue = (children: ReactNode): string => {
+ let value = '';
+ Children.forEach(children, (child: ReactNode) => {
+ if (
+ isValidElement(child)
+ && child.type === 'option'
+ && (!value || child.props.selected)
+ ) {
+ value = child.props.value || 'initial';
+ }
+ });
+
+ return value;
+}
+
+const SelectFieldInput = React.forwardRef((
+ {
+ children,
+ defaultValue,
+ value,
+ onChange,
+ ...rest
+ },
+ ref
+): React.ReactElement => {
+ const fieldContext = useContext(FieldContext);
+ const [inputValue, setInputValue] = useState | number>('');
+
+ useEffect(() => {
+ const initialValue = value || defaultValue || determineInitialValue(children);
+ fieldContext.changeValue(!!initialValue);
+ setInputValue(initialValue.toString());
+ }, [value, defaultValue]);
+
+ return (
+
+ {({ changeValue, changeFocus, stateIcon }) => (
+
+
+ {stateIcon}
+
+ )}
+
+ );
+});
+
+SelectFieldInput.displayName = 'SelectFieldInput';
+SelectFieldInput.propTypes = propTypes;
+
+export default SelectFieldInput;
diff --git a/src/components/SelectField/index.ts b/src/components/SelectField/index.ts
new file mode 100644
index 0000000..3b49bb8
--- /dev/null
+++ b/src/components/SelectField/index.ts
@@ -0,0 +1 @@
+export { default as SelectField } from './SelectField';
diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx
new file mode 100644
index 0000000..b2a3d72
--- /dev/null
+++ b/src/components/TextField/TextField.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import FieldBase, { propTypes as basePropTypes } from '@/components/Field/FieldBase';
+import FieldBaseProps from '@/components/Field/FieldBase';
+import TextFieldInput from '@/components/TextField/TextFieldInput';
+
+export type TextFieldProps = React.InputHTMLAttributes & FieldBaseProps;
+
+const TextField = React.forwardRef((
+ {
+ actions,
+ label,
+ valid,
+ variant,
+ ...rest
+ },
+ ref
+): React.ReactElement => (
+
+
+
+));
+
+export const propTypes = {
+ ...basePropTypes,
+ type: PropTypes.oneOf(['password', 'text', 'reset'])
+}
+
+TextField.displayName = 'TextField';
+TextField.propTypes = propTypes;
+
+export default TextField;
diff --git a/src/components/TextField/TextFieldInput.tsx b/src/components/TextField/TextFieldInput.tsx
new file mode 100644
index 0000000..d24388b
--- /dev/null
+++ b/src/components/TextField/TextFieldInput.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import { FieldContext, FieldContextProps } from '@/components/Field/FieldContext';
+import { propTypes, TextFieldProps } from './TextField';
+import { useContext, useEffect, useState } from 'react';
+import FieldContainer from '@/components/Field/FieldContainer';
+
+const TextFieldInput = React.forwardRef((
+ {
+ type,
+ value,
+ onChange,
+ ...rest
+ },
+ ref
+): React.ReactElement => {
+ const fieldContext = useContext(FieldContext);
+ const [inputValue, setInputValue] = useState | number>('');
+
+ useEffect(() => {
+ setInputValue(value || '');
+ fieldContext.changeValue(!!value);
+ }, [value]);
+ return (
+
+ {({ changeValue, changeFocus, stateIcon }) => (
+
+ changeFocus(true)}
+ onBlur={() => changeFocus(false)}
+ className="form-field"
+ onChange={(event: React.ChangeEvent) => {
+ if (onChange) {
+ onChange(event);
+ }
+ setInputValue(event.target.value);
+ changeValue(!!event.target.value);
+ }}
+ {...rest}
+ />
+ {stateIcon}
+
+ )}
+
+ )
+});
+
+TextFieldInput.displayName = 'TextFieldInput';
+TextFieldInput.propTypes = propTypes;
+
+export default TextFieldInput;
diff --git a/src/components/TextField/index.ts b/src/components/TextField/index.ts
new file mode 100644
index 0000000..f38be84
--- /dev/null
+++ b/src/components/TextField/index.ts
@@ -0,0 +1 @@
+export { default as TextField } from '@/components/TextField/TextField';
diff --git a/src/components/index.ts b/src/components/index.ts
index b3c70ae..22c7f1e 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -6,3 +6,5 @@ export * from './Page';
export * from './Panel';
export * from './Icon';
export * from './utils';
+export * from './TextField';
+export * from './SelectField';
diff --git a/src/style/base/_typography.scss b/src/style/base/_typography.scss
index 69b3e08..893f317 100644
--- a/src/style/base/_typography.scss
+++ b/src/style/base/_typography.scss
@@ -124,6 +124,7 @@ h5 {
font-size: 1.2rem;
margin: 0 0 1rem;
color: mixins.color('gray', 900);
+ font-weight: 600;
}
h6 {
diff --git a/src/style/base/_variables.scss b/src/style/base/_variables.scss
index 6f9fc22..41b28a7 100644
--- a/src/style/base/_variables.scss
+++ b/src/style/base/_variables.scss
@@ -8,8 +8,8 @@ $base-background-color: mixins.color('gray', 50);
$base-body-gutter: 0;
$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);
+$bse-border-hover-color: mixins.color('gray', 400);
$base-border-radius: 4px;
$base-gutter: 1rem;
$base-gutter-steps: 0.25;
@@ -22,12 +22,12 @@ $breakpoints: (
);
:root {
- --base-font-size: #{$base-font-size};
--base-body-background: #{$base-background-color};
--base-body-gutter: #{$base-body-gutter};
--base-font-color: #{$base-font-color};
--base-font-family: #{$base-font-family};
--base-border-color: #{$base-border-color};
+ --base-border-hover-color: #{$bse-border-hover-color};
--base-border-radius: #{$base-border-radius};
--base-gutter: #{$base-gutter};
}
@@ -49,6 +49,10 @@ $danger-color-hover: mixins.color('red', 800);
$danger-color-active: mixins.color('red', 900);
$danger-color-light-background: mixins.color('red', 50);
+$success-color: mixins.color('green', 700);
+$success-color-hover: mixins.color('green', 800);
+$success-color-active: mixins.color('green', 900);
+
:root {
--primary-color: #{$primary-color};
--primary-color-hover: #{$primary-color-hover};
@@ -62,7 +66,11 @@ $danger-color-light-background: mixins.color('red', 50);
--danger-color: #{$danger-color};
--danger-color-hover: #{$danger-color-hover};
--danger-color-active: #{$danger-color-active};
- --danger-color-light-background: #{$danger-color-light-background}
+ --danger-color-light-background: #{$danger-color-light-background};
+
+ --success-color: #{$success-color};
+ --success-color-hover: #{$success-color};
+ --success-color-active: #{$success-color-active}
}
/**
@@ -88,7 +96,7 @@ $page-gutter: 2rem;
/**
* 5. Button
*/
-$button-padding: 0.9rem;
+$button-padding: 1rem;
$button-font-weight: 600;
$button-font-size: 1rem;
@@ -139,7 +147,7 @@ $danger-button-ghost-active-background: mixins.color('red', 75);
/**
* 6. Cards
*/
-$card-padding: 1rem;
+$card-padding: 1.5rem;
$card-background: #fff;
:root {
@@ -414,3 +422,16 @@ $icons: (
@function icon($name) {
@return map-get($icons, $name);
}
+
+/**
+ * 8. Form fields
+ */
+$form-field-padding: 1rem;
+$form-field-font-weight: 500;
+$form-field-font-size: 1rem;
+
+:root {
+ --form-field-padding: #{$form-field-padding};
+ --form-field-font-weight: #{$form-field-font-weight};
+ --form-field-font-size: #{$form-field-font-size};
+}
diff --git a/src/style/components/_button.scss b/src/style/components/_button.scss
index 55f64d3..715a77f 100644
--- a/src/style/components/_button.scss
+++ b/src/style/components/_button.scss
@@ -1,4 +1,5 @@
.btn {
+ background: transparent;
border: solid 1px transparent;
border-radius: var(--base-border-radius);
cursor: pointer;
diff --git a/src/style/components/_card.scss b/src/style/components/_card.scss
index ad92d8b..fc2e89c 100644
--- a/src/style/components/_card.scss
+++ b/src/style/components/_card.scss
@@ -5,6 +5,7 @@
background: var(--card-background);
border: solid 1px var(--base-border-color);
border-radius: var(--base-border-radius);
+ width: 100%;
.card-content {
padding: var(--card-padding);
@@ -22,16 +23,12 @@
margin-left: var(--card-padding);
}
}
-
- & + .card-content {
- padding-top: 0;
- }
}
.card-title {
font: {
weight: 600;
- size: 1.1rem;
+ size: 1.2rem;
}
}
diff --git a/src/style/components/_dropdown.scss b/src/style/components/_dropdown.scss
new file mode 100644
index 0000000..6d8df4a
--- /dev/null
+++ b/src/style/components/_dropdown.scss
@@ -0,0 +1,2 @@
+
+
diff --git a/src/style/components/_form-fields.scss b/src/style/components/_form-fields.scss
new file mode 100644
index 0000000..f33df82
--- /dev/null
+++ b/src/style/components/_form-fields.scss
@@ -0,0 +1,173 @@
+@use "../base/mixins";
+@use "../base/variables";
+@use "icons";
+
+.form-field-base {
+ background: #fff;
+ border: solid 1px var(--base-border-color);
+ border-radius: var(--base-border-radius);
+ pointer-events: none;
+ position: relative;
+ width: auto;
+ display: flex;
+ transition: all .1s ease-in-out;
+
+ .form-field-actions {
+ align-self: center;
+ pointer-events: auto;
+ padding-right: var(--form-field-padding);
+ }
+
+ &:hover:not(.form-field-valid):not(.form-field-invalid) {
+ border-color: var(--base-border-hover-color);
+ }
+
+ &.focused:not(.form-field-valid):not(.form-field-invalid) {
+ border-color: var(--primary-color);
+
+ .form-field-label-floating {
+ color: var(--primary-color);
+ }
+ }
+
+ &.focused, &.has-value {
+ .form-field-label-floating {
+ transform: translateY(calc((var(--form-field-padding) / 2) / 2));
+ top: 0;
+ font: {
+ size: 0.8rem;
+ }
+ }
+ }
+
+ &.floating-label .form-field {
+ padding: calc(var(--form-field-padding) + (var(--form-field-padding) / 2)) var(--form-field-padding) calc(var(--form-field-padding) / 2);
+ }
+
+ &:not(.floating-label) .form-field {
+ padding: var(--form-field-padding);
+ }
+
+ .form-field {
+ background: none;
+ border-radius: var(--base-border-radius);
+ border: none;
+ outline: none;
+ width: 100%;
+ font: {
+ size: var(--form-field-font-size);
+ weight: var(--form-field-font-weight);
+ }
+ pointer-events: auto;
+ line-height: 1.3rem;
+ }
+
+ .form-field-label-floating {
+ font: {
+ size: var(--form-field-font-size);
+ }
+ color: mixins.color('gray', 700);
+ position: absolute;
+ top: 50%;
+ left: var(--form-field-padding);
+ transform: translateY(-50%);
+ transition: all .1s ease-in-out;
+ }
+
+ & + .form-message {
+ margin-top: 0.25rem;
+ }
+
+ .field-state-icon {
+ @extend .icon;
+
+ position: absolute;
+ justify-content: center;
+ right: var(--form-field-padding);
+ align-items: center;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+
+ &.form-field-invalid {
+ border-color: var(--danger-color);
+
+ .form-field-label-floating {
+ color: var(--danger-color);
+ }
+
+ .form-field {
+ padding-right: calc(var(--form-field-padding) * 3);
+ }
+
+ .field-state-icon {
+ color: var(--danger-color)
+ }
+ }
+
+ &.form-field-valid {
+ border-color: var(--success-color);
+
+ .form-field-label-floating {
+ color: var(--success-color);
+ }
+
+ .form-field {
+ padding-right: calc(var(--form-field-padding) * 3);
+ }
+
+ .field-state-icon {
+ color: var(--success-color)
+ }
+ }
+
+ &.form-field-invalid .form-field-container .form-field,
+ &.form-field-valid .form-field-container .form-field {
+ padding-right: calc(var(--form-field-padding) * 3);
+ }
+
+ .form-field-container {
+ flex: 1 1 100%;
+ display: flex;
+ position: relative;
+
+ &.toggles {
+ position: relative;
+
+ &:before {
+ content: variables.icon('arrow-alt-down');
+ font-family: 'Cui-Icons';
+ color: inherit;
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ right: var(--form-field-padding);
+ }
+
+ .form-field-select {
+ -moz-appearance: none;
+ -webkit-appearance: none;
+
+ &::-ms-expand {
+ display: none;
+ }
+ }
+
+ .field-state-icon {
+ right: calc(var(--form-field-padding) * 2);
+ }
+ }
+ }
+
+ &.form-field-base-toggles {
+ &.form-field-valid, &.form-field-invalid {
+ .field-state-icon {
+ right: calc(var(--form-field-padding) * 2.5);
+ }
+
+ .form-field-container.toggles .form-field {
+ padding-right: calc(var(--form-field-padding) * 4.5);
+ }
+ }
+ }
+}
diff --git a/src/style/components/_forms.scss b/src/style/components/_forms.scss
new file mode 100644
index 0000000..01695ab
--- /dev/null
+++ b/src/style/components/_forms.scss
@@ -0,0 +1,22 @@
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-label {
+ font: {
+ weight: 600;
+ }
+ display: inline-block;
+ margin-bottom: 0.5rem;
+}
+
+.form-message {
+ &.form-message-valid {
+ color: var(--success-color);
+ }
+
+ &.form-message-invalid {
+ font-weight: 500;
+ color: var(--danger-color);
+ }
+}
diff --git a/src/style/components/_icons.scss b/src/style/components/_icons.scss
index 7ac870c..461a0c6 100644
--- a/src/style/components/_icons.scss
+++ b/src/style/components/_icons.scss
@@ -4,6 +4,7 @@
font: {
family: 'Cui-Icons';
style: normal;
+ size: 1.3rem;
}
line-height: 1;
display: inline-block;
diff --git a/src/style/index.scss b/src/style/index.scss
index 5369252..abad4b0 100644
--- a/src/style/index.scss
+++ b/src/style/index.scss
@@ -13,8 +13,10 @@
@use "components/button";
@use "components/page";
@use "components/panel";
+@use "components/form-fields";
@use "components/card";
@use "components/icons";
+@use "components/forms";
/**
* 3. Layout
diff --git a/tsconfig.json b/tsconfig.json
index f2acd7d..8337dc1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,10 +4,10 @@
"module": "esnext",
"target": "es5",
"lib": [
- "es6",
- "dom",
- "es2016",
- "es2017"
+ "ES6",
+ "DOM",
+ "ES2016",
+ "ES2017"
],
"sourceMap": true,
"jsx": "react",
diff --git a/www/src/index.tsx b/www/src/index.tsx
index f6c8498..54da55d 100644
--- a/www/src/index.tsx
+++ b/www/src/index.tsx
@@ -2,1297 +2,1647 @@ import * as React from 'react';
import * as ReactDom from 'react-dom';
import '../../src/style/index.scss';
-import { Button, Card, Col, Grid, Icon, Page, Row, Variant } from '../../src';
+import { Button, Card, Col, Grid, Icon, Page, Row, SelectField, TextField, Variant } from '../../src';
import Container from '../../src/components/Container/Container';
+import FormGroup from '../../src/components/Form/Group';
+import FormLabel from '../../src/components/Form/Label';
+import FormMessage from '../../src/components/Form/Message';
+import { useState } from 'react';
+
+const TestControllable = () => {
+ const [value, setValue] = useState('');
+
+ return (
+ <>
+ Controllable inputs
+
+ ) => setValue(event.target.value)}
+ />
+
+
+
+
+ >
+ );
+}
ReactDom.render(
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }>Icon left
-
-
- }>Icon right
-
-
-
-
-
-
-
-
-
- Col 1/1
-
-
- Col 1/2
- Col 2/2
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }>Icon left
+
+
+ }>Icon right
+
+
+
+
+
+
+
+
+
+
+
+ Col 1/1
+
+
+ Col 1/2
+ Col 2/2
+
+
+
+ Col 1/3
+ Col 2/3
+ Col 3/4
+
+
+
+ Col 1/3
+ Col 2/3
+ Col 3/4
+
-
- Col 1/3
- Col 2/3
- Col 3/4
-
+
+ Col 1/4
+ Col 2/4
+ Col 3/4
+ Col 4/4
+
+
+
+
+
+
+
+
+
+
+ Standard input
+ Username
+
+
-
- Col 1/3
- Col 2/3
- Col 3/4
-
+
+ Input with floating label
+
+
-
- Col 1/4
- Col 2/4
- Col 3/4
- Col 4/4
-
-
-
-
-
-
-
- Activity
-
-
-
-
- Alert circle
-
-
-
-
- Alert triangle
-
-
-
-
- Archive
-
-
-
-
- Arrow left
-
-
-
-
- Arrow circle down
-
-
-
-
- Arrow circle left
-
-
-
-
- Arrow circle right
-
-
-
-
- Arrow circle up
-
-
-
-
- Sort down
-
-
-
-
- Sort down
-
-
-
-
- Arrow right
-
-
-
-
- Arrow alt left
-
-
-
-
- Arrow alt down
-
-
-
-
- Arrow alt right
-
-
-
-
- Arrow alt up
-
-
-
-
- Sort left
-
-
-
-
- Sort right
-
-
-
-
- Sort up
-
-
-
-
- Arrow up
-
-
-
-
- Arrowhead down
-
-
-
-
- Arrowhead left
-
-
-
-
- Arrowhead right
-
-
-
-
- Arrowhead up
-
-
-
-
- At
-
-
-
-
- Attach 2
-
-
-
-
- Attach
-
-
-
-
- Award
-
-
-
-
- Backspace
-
-
-
-
- Bar chart 2
-
-
-
-
- Bar chart
-
-
-
-
- Battery
-
-
-
-
- Behance
-
-
-
-
- Bell off
-
-
-
-
- Bell
-
-
-
-
- Bluetooth
-
-
-
-
- Book open
-
-
-
-
- Book
-
-
-
-
- Bookmark
-
-
-
-
- Briefcase
-
-
-
-
- Browser
-
-
-
-
- Brush
-
-
-
-
- Bulb
-
-
-
-
- Calendar
-
-
-
-
- Camera
-
-
-
-
- Car
-
-
-
-
- Cast
-
-
-
-
- Charging
-
-
-
-
- Checkmark circle 2
-
-
-
-
- Checkmark circle
-
-
-
-
- Checkmark
-
-
-
-
- Checkmark square 2
-
-
-
-
- Checkmark square
-
-
-
-
- Chevron down
-
-
-
-
- Chevron left
-
-
-
-
- Chevron right
-
-
-
-
- Chevron up
-
-
-
-
- Clipboard
-
-
-
-
- Clock
-
-
-
-
- Close circle
-
-
-
-
- Close
-
-
-
-
- Close square
-
-
-
-
- Cloud download
-
-
-
-
- Cloud upload
-
-
-
-
- Code download
-
-
-
-
- Code
-
-
-
-
- Collapse
-
-
-
-
- Color palette
-
-
-
-
- Color picker
-
-
-
-
- Compass
-
-
-
-
- Copy
-
-
-
-
- Corner down left
-
-
-
-
- Corner down right
-
-
-
-
- Corner left down
-
-
-
-
- Corner left up
-
-
-
-
- Corner right down
-
-
-
-
- Corner right up
-
-
-
-
- Corner up left
-
-
-
-
- Corner up right
-
-
-
-
- Credit card
-
-
-
-
- Crop
-
-
-
-
- Cube
-
-
-
-
- Diagonal arrow left down
-
-
-
-
- Diagonal arrow left up
-
-
-
-
- Diagonal arrow right down
-
-
-
-
- Diagonal arrow right up
-
-
-
-
- Done all
-
-
-
-
- Download
-
-
-
-
- Droplet off
-
-
-
-
- Droplet
-
-
-
-
- Edit 2
-
-
-
-
- Edit
-
-
-
-
- Email
-
-
-
-
- Expand
-
-
-
-
- External link
-
-
-
-
- Eye off 2
-
-
-
-
- Eye off
-
-
-
-
- Eye
-
-
-
-
- Facebook
-
-
-
-
- File add
-
-
-
-
- File
-
-
-
-
- File remove
-
-
-
-
- File text
-
-
-
-
- Film
-
-
-
-
- Flag
-
-
-
-
- Flash off
-
-
-
-
- Flash
-
-
-
-
- Flip 2
-
-
-
-
- Flip
-
-
-
-
- Folder add
-
-
-
-
- Folder
-
-
-
-
- Folder remove
-
-
-
-
- Funnel
-
-
-
-
- Gift
-
-
-
-
- Github
-
-
-
-
- Globe 2
-
-
-
-
- Globe
-
-
-
-
- Google
-
-
-
-
- Grid
-
-
-
-
- Hard drive
-
-
-
-
- Hash
-
-
-
-
- Headphones
-
-
-
-
- Heart
-
-
-
-
- Home
-
-
-
-
- Image
-
-
-
-
- Inbox
-
-
-
-
- Info
-
-
-
-
- Keypad
-
-
-
-
- Layers
-
-
-
-
- Layout
-
-
-
-
- Link 2
-
-
-
-
- Link
-
-
-
-
- Linkedin
-
-
-
-
- List
-
-
-
-
- Loader
-
-
-
-
- Lock
-
-
-
-
- Log in
-
-
-
-
- Log out
-
-
-
-
- Map
-
-
-
-
- Maximize
-
-
-
-
- Menu 2
-
-
-
-
- Menu arrow
-
-
-
-
- Menu
-
-
-
-
- Message circle
-
-
-
-
- Message square
-
-
-
-
- Mic off
-
-
-
-
- Mic
-
-
-
-
- Minimize
-
-
-
-
- Minus circle
-
-
-
-
- Minus
-
-
-
-
- Minus square
-
-
-
-
- Monitor
-
-
-
-
- Moon
-
-
-
-
- More horizontal
-
-
-
-
- More vertical
-
-
-
-
- Move
-
-
-
-
- Music
-
-
-
-
- Navigation 2
-
-
-
-
- Navigation
-
-
-
-
- Npm
-
-
-
-
- Options 2
-
-
-
-
- Options
-
-
-
-
- Pantone
-
-
-
-
- Paper plane
-
-
-
-
- Pause circle
-
-
-
-
- People
-
-
-
-
- Percent
-
-
-
-
- user add
-
-
-
-
- user delete
-
-
-
-
- user done
-
-
-
-
- user
-
-
-
-
- user remove
-
-
-
-
- Phone call
-
-
-
-
- Phone missed
-
-
-
-
- Phone off
-
-
-
-
- Phone
-
-
-
-
- Pie chart
-
-
-
-
- Pin
-
-
-
-
- Play circle
-
-
-
-
- Plus circle
-
-
-
-
- Plus
-
-
-
-
- Plus square
-
-
-
-
- Power
-
-
-
-
- Pricetags
-
-
-
-
- Printer
-
-
-
-
- Question mark circle
-
-
-
-
- Question mark
-
-
-
-
- Radio button off
-
-
-
-
- Radio button on
-
-
-
-
- Radio
-
-
-
-
- Recording
-
-
-
-
- Refresh
-
-
-
-
- Repeat
-
-
-
-
- Rewind left
-
-
-
-
- Rewind right
-
-
-
-
- Save
-
-
-
-
- Scissors
-
-
-
-
- Search
-
-
-
-
- Settings 2
-
-
-
-
- Settings
-
-
-
-
- Shake
-
-
-
-
- Share
-
-
-
-
- Shield off
-
-
-
-
- Shield
-
-
-
-
- Shopping bag
-
-
-
-
- Shopping cart
-
-
-
-
- Shuffle 2
-
-
-
-
- Shuffle
-
-
-
-
- Step backwards
-
-
-
-
- Step forward
-
-
-
-
- Slash
-
-
-
-
- Smartphone
-
-
-
-
- Smiling face
-
-
-
-
- Speaker
-
-
-
-
- Square
-
-
-
-
- Star
-
-
-
-
- Stop circle
-
-
-
-
- Sun
-
-
-
-
- Swap
-
-
-
-
- Sync
-
-
-
-
- Text
-
-
-
-
- Thermometer minus
-
-
-
-
- Thermometer
-
-
-
-
- Thermometer plus
-
-
-
-
- Toggle left
-
-
-
-
- Toggle right
-
-
-
-
- Trash 2
-
-
-
-
- Trash
-
-
-
-
- Trending down
-
-
-
-
- Trending up
-
-
-
-
- Tv
-
-
-
-
- Twitter
-
-
-
-
- Umbrella
-
-
-
-
- Undo
-
-
-
-
- Unlock
-
-
-
-
- Upload
-
-
-
-
- Video off
-
-
-
-
- Video
-
-
-
-
- Volume down
-
-
-
-
- Volume mute
-
-
-
-
- Volume off
-
-
-
-
- Volume up
-
-
-
-
- Wifi off
-
-
-
-
- Wifi
-
-
-
-
+
+ Input with default value
+
+
+
+
+ Input with actions
+ {
+
+ }}
+ actions={[
+
+ ]}
+ valid={true}
+ />
+
+
+
+ Invalid input
+
+
+ Input is invalid
+
+
+
+
+ Valid input
+
+
+
+ Valid input
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Activity
+
+
+
+
+
+ Alert circle
+
+
+
+
+
+ Alert triangle
+
+
+
+
+
+ Archive
+
+
+
+
+
+ Arrow left
+
+
+
+
+
+ Arrow circle down
+
+
+
+
+
+ Arrow circle left
+
+
+
+
+
+ Arrow circle right
+
+
+
+
+
+ Arrow circle up
+
+
+
+
+
+ Sort down
+
+
+
+
+
+ Sort down
+
+
+
+
+
+ Arrow right
+
+
+
+
+
+ Arrow alt left
+
+
+
+
+
+ Arrow alt down
+
+
+
+
+
+ Arrow alt right
+
+
+
+
+
+ Arrow alt up
+
+
+
+
+
+ Sort left
+
+
+
+
+
+ Sort right
+
+
+
+
+
+ Sort up
+
+
+
+
+
+ Arrow up
+
+
+
+
+
+ chevron-double down
+
+
+
+
+
+ chevron-double left
+
+
+
+
+
+ chevron-double right
+
+
+
+
+
+ chevron-double up
+
+
+
+
+
+ At
+
+
+
+
+
+ Attach 2
+
+
+
+
+
+ Attach
+
+
+
+
+
+ Award
+
+
+
+
+
+ Backspace
+
+
+
+
+
+ Bar chart 2
+
+
+
+
+
+ Bar chart
+
+
+
+
+
+ Battery
+
+
+
+
+
+ Behance
+
+
+
+
+
+ Bell off
+
+
+
+
+
+ Bell
+
+
+
+
+
+ Bluetooth
+
+
+
+
+
+ Book open
+
+
+
+
+
+ Book
+
+
+
+
+
+ Bookmark
+
+
+
+
+
+ Briefcase
+
+
+
+
+
+ Browser
+
+
+
+
+
+ Brush
+
+
+
+
+
+ Bulb
+
+
+
+
+
+ Calendar
+
+
+
+
+
+ Camera
+
+
+
+
+
+ Car
+
+
+
+
+
+ Cast
+
+
+
+
+
+ Charging
+
+
+
+
+
+ Checkmark circle 2
+
+
+
+
+
+ Checkmark circle
+
+
+
+
+
+ Checkmark
+
+
+
+
+
+ Checkmark square 2
+
+
+
+
+
+ Checkmark square
+
+
+
+
+
+ Chevron down
+
+
+
+
+
+ Chevron left
+
+
+
+
+
+ Chevron right
+
+
+
+
+
+ Chevron up
+
+
+
+
+
+ Clipboard
+
+
+
+
+
+ Clock
+
+
+
+
+
+ Close circle
+
+
+
+
+
+ Close
+
+
+
+
+
+ Close square
+
+
+
+
+
+ Cloud download
+
+
+
+
+
+ Cloud upload
+
+
+
+
+
+ Code download
+
+
+
+
+
+ Code
+
+
+
+
+
+ Collapse
+
+
+
+
+
+ Color palette
+
+
+
+
+
+ Color picker
+
+
+
+
+
+ Compass
+
+
+
+
+
+ Copy
+
+
+
+
+
+ Corner down left
+
+
+
+
+
+ Corner down right
+
+
+
+
+
+ Corner left down
+
+
+
+
+
+ Corner left up
+
+
+
+
+
+ Corner right down
+
+
+
+
+
+ Corner right up
+
+
+
+
+
+ Corner up left
+
+
+
+
+
+ Corner up right
+
+
+
+
+
+ Credit card
+
+
+
+
+
+ Crop
+
+
+
+
+
+ Cube
+
+
+
+
+
+ Diagonal arrow left down
+
+
+
+
+
+ Diagonal arrow left up
+
+
+
+
+
+ Diagonal arrow right down
+
+
+
+
+
+ Diagonal arrow right up
+
+
+
+
+
+ Done all
+
+
+
+
+
+ Download
+
+
+
+
+
+ Droplet off
+
+
+
+
+
+ Droplet
+
+
+
+
+
+ Edit 2
+
+
+
+
+
+ Edit
+
+
+
+
+
+ Email
+
+
+
+
+
+ Expand
+
+
+
+
+
+ External link
+
+
+
+
+
+ Eye off 2
+
+
+
+
+
+ Eye off
+
+
+
+
+
+ Eye
+
+
+
+
+
+ Facebook
+
+
+
+
+
+ File add
+
+
+
+
+
+ File
+
+
+
+
+
+ File remove
+
+
+
+
+
+ File text
+
+
+
+
+
+ Film
+
+
+
+
+
+ Flag
+
+
+
+
+
+ Flash off
+
+
+
+
+
+ Flash
+
+
+
+
+
+ Flip 2
+
+
+
+
+
+ Flip
+
+
+
+
+
+ Folder add
+
+
+
+
+
+ Folder
+
+
+
+
+
+ Folder remove
+
+
+
+
+
+ Funnel
+
+
+
+
+
+ Gift
+
+
+
+
+
+ Github
+
+
+
+
+
+ Globe 2
+
+
+
+
+
+ Globe
+
+
+
+
+
+ Google
+
+
+
+
+
+ Grid
+
+
+
+
+
+ Hard drive
+
+
+
+
+
+ Hash
+
+
+
+
+
+ Headphones
+
+
+
+
+
+ Heart
+
+
+
+
+
+ Home
+
+
+
+
+
+ Image
+
+
+
+
+
+ Inbox
+
+
+
+
+
+ Info
+
+
+
+
+
+ Keypad
+
+
+
+
+
+ Layers
+
+
+
+
+
+ Layout
+
+
+
+
+
+ Link 2
+
+
+
+
+
+ Link
+
+
+
+
+
+ Linkedin
+
+
+
+
+
+ List
+
+
+
+
+
+ Loader
+
+
+
+
+
+ Lock
+
+
+
+
+
+ Log in
+
+
+
+
+
+ Log out
+
+
+
+
+
+ Map
+
+
+
+
+
+ Maximize
+
+
+
+
+
+ Menu 2
+
+
+
+
+
+ Menu arrow
+
+
+
+
+
+ Menu
+
+
+
+
+
+ Message circle
+
+
+
+
+
+ Message square
+
+
+
+
+
+ Mic off
+
+
+
+
+
+ Mic
+
+
+
+
+
+ Minimize
+
+
+
+
+
+ Minus circle
+
+
+
+
+
+ Minus
+
+
+
+
+
+ Minus square
+
+
+
+
+
+ Monitor
+
+
+
+
+
+ Moon
+
+
+
+
+
+ More horizontal
+
+
+
+
+
+ More vertical
+
+
+
+
+
+ Move
+
+
+
+
+
+ Music
+
+
+
+
+
+ Navigation 2
+
+
+
+
+
+ Navigation
+
+
+
+
+
+ Npm
+
+
+
+
+
+ Options 2
+
+
+
+
+
+ Options
+
+
+
+
+
+ Pantone
+
+
+
+
+
+ Paper plane
+
+
+
+
+
+ Pause circle
+
+
+
+
+
+ People
+
+
+
+
+
+ Percent
+
+
+
+
+
+ user add
+
+
+
+
+
+ user delete
+
+
+
+
+
+ user done
+
+
+
+
+
+ user
+
+
+
+
+
+ user remove
+
+
+
+
+
+ Phone call
+
+
+
+
+
+ Phone missed
+
+
+
+
+
+ Phone off
+
+
+
+
+
+ Phone
+
+
+
+
+
+ Pie chart
+
+
+
+
+
+ Pin
+
+
+
+
+
+ Play circle
+
+
+
+
+
+ Plus circle
+
+
+
+
+
+ Plus
+
+
+
+
+
+ Plus square
+
+
+
+
+
+ Power
+
+
+
+
+
+ Pricetags
+
+
+
+
+
+ Printer
+
+
+
+
+
+ Question mark circle
+
+
+
+
+
+ Question mark
+
+
+
+
+
+ Radio button off
+
+
+
+
+
+ Radio button on
+
+
+
+
+
+ Radio
+
+
+
+
+
+ Recording
+
+
+
+
+
+ Refresh
+
+
+
+
+
+ Repeat
+
+
+
+
+
+ Rewind left
+
+
+
+
+
+ Rewind right
+
+
+
+
+
+ Save
+
+
+
+
+
+ Scissors
+
+
+
+
+
+ Search
+
+
+
+
+
+ Settings 2
+
+
+
+
+
+ Settings
+
+
+
+
+
+ Shake
+
+
+
+
+
+ Share
+
+
+
+
+
+ Shield off
+
+
+
+
+
+ Shield
+
+
+
+
+
+ Shopping bag
+
+
+
+
+
+ Shopping cart
+
+
+
+
+
+ Shuffle 2
+
+
+
+
+
+ Shuffle
+
+
+
+
+
+ Step backwards
+
+
+
+
+
+ Step forward
+
+
+
+
+
+ Slash
+
+
+
+
+
+ Smartphone
+
+
+
+
+
+ Smiling face
+
+
+
+
+
+ Speaker
+
+
+
+
+
+ Square
+
+
+
+
+
+ Star
+
+
+
+
+
+ Stop circle
+
+
+
+
+
+ Sun
+
+
+
+
+
+ Swap
+
+
+
+
+
+ Sync
+
+
+
+
+
+ Text
+
+
+
+
+
+ Thermometer minus
+
+
+
+
+
+ Thermometer
+
+
+
+
+
+ Thermometer plus
+
+
+
+
+
+ Toggle left
+
+
+
+
+
+ Toggle right
+
+
+
+
+
+ Trash 2
+
+
+
+
+
+ Trash
+
+
+
+
+
+ Trending down
+
+
+
+
+
+ Trending up
+
+
+
+
+
+ Tv
+
+
+
+
+
+ Twitter
+
+
+
+
+
+ Umbrella
+
+
+
+
+
+ Undo
+
+
+
+
+
+ Unlock
+
+
+
+
+
+ Upload
+
+
+
+
+
+ Video off
+
+
+
+
+
+ Video
+
+
+
+
+
+ Volume down
+
+
+
+
+
+ Volume mute
+
+
+
+
+
+ Volume off
+
+
+
+
+
+ Volume up
+
+
+
+
+
+ Wifi off
+
+
+
+
+
+ Wifi
+
+
+
+
+
+
,
document.getElementById('root')