diff --git a/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..3d56855d05
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+`;
diff --git a/__tests__/shared/components/GUIKit/Checkbox/index.jsx b/__tests__/shared/components/GUIKit/Checkbox/index.jsx
new file mode 100644
index 0000000000..e693ad4989
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Checkbox/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import Checkbox from 'components/GUIKit/Checkbox';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..6bf8ad76cf
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap
@@ -0,0 +1,118 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+
+ }
+ date={null}
+ daySize={53}
+ disableScroll={false}
+ disabled={false}
+ displayFormat="MMM DD, YYYY"
+ enableOutsideDays={true}
+ firstDayOfWeek={1}
+ focused={false}
+ hideKeyboardShortcutsPanel={true}
+ horizontalMargin={0}
+ horizontalMonthPadding={13}
+ id="null----false"
+ initialVisibleMonth={null}
+ inputIconPosition="after"
+ isDayBlocked={[Function]}
+ isDayHighlighted={[Function]}
+ isOutsideRange={[Function]}
+ isRTL={false}
+ keepFocusOnInput={false}
+ keepOpenOnDateSelect={false}
+ monthFormat="MMMM YYYY"
+ navNext={
+
+ }
+ navPrev={
+
+ }
+ noBorder={false}
+ numberOfMonths={1}
+ onClose={[Function]}
+ onDateChange={[Function]}
+ onFocusChange={[Function]}
+ onNextMonthClick={[Function]}
+ onPrevMonthClick={[Function]}
+ openDirection="down"
+ orientation="horizontal"
+ phrases={
+ Object {
+ "calendarLabel": "Calendar",
+ "chooseAvailableDate": [Function],
+ "clearDate": "Clear Date",
+ "closeDatePicker": "Close",
+ "dateIsSelected": [Function],
+ "dateIsUnavailable": [Function],
+ "enterKey": "Enter key",
+ "escape": "Escape key",
+ "hideKeyboardShortcutsPanel": "Close the shortcuts panel.",
+ "homeEnd": "Home and end keys",
+ "jumpToNextMonth": "Move forward to switch to the next month.",
+ "jumpToPrevMonth": "Move backward to switch to the previous month.",
+ "keyboardNavigationInstructions": "Press the down arrow key to interact with the calendar and
+ select a date. Press the question mark key to get the keyboard shortcuts for changing dates.",
+ "keyboardShortcuts": "Keyboard Shortcuts",
+ "leftArrowRightArrow": "Right and left arrow keys",
+ "moveFocusByOneDay": "Move backward (left) and forward (right) by one day.",
+ "moveFocusByOneMonth": "Switch months.",
+ "moveFocusByOneWeek": "Move backward (up) and forward (down) by one week.",
+ "moveFocustoStartAndEndOfWeek": "Go to the first or last day of a week.",
+ "openThisPanel": "Open this panel.",
+ "pageUpPageDown": "page up and page down keys",
+ "questionMark": "Question mark",
+ "returnFocusToInput": "Return to the date input field.",
+ "selectFocusedDate": "Select the date in focus.",
+ "showKeyboardShortcutsPanel": "Open the keyboard shortcuts panel.",
+ "upArrowDownArrow": "up and down arrow keys",
+ }
+ }
+ placeholder=""
+ readOnly={false}
+ regular={false}
+ renderCalendarInfo={null}
+ renderDayContents={[Function]}
+ renderMonthElement={null}
+ renderMonthText={null}
+ reopenPickerOnClearDate={false}
+ required={false}
+ screenReaderInputMessage=""
+ showClearDate={false}
+ showDefaultInputIcon={false}
+ small={false}
+ verticalHeight={null}
+ verticalSpacing={22}
+ weekDayFormat="ddd"
+ withFullScreenPortal={false}
+ withPortal={false}
+ />
+
+`;
diff --git a/__tests__/shared/components/GUIKit/Datepicker/index.jsx b/__tests__/shared/components/GUIKit/Datepicker/index.jsx
new file mode 100644
index 0000000000..c0001cce99
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Datepicker/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import Datepicker from 'components/GUIKit/Datepicker';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/Dropdown/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Dropdown/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..1108c3a813
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Dropdown/__snapshots__/index.jsx.snap
@@ -0,0 +1,77 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+
+
+

+
+
+`;
diff --git a/__tests__/shared/components/GUIKit/Dropdown/index.jsx b/__tests__/shared/components/GUIKit/Dropdown/index.jsx
new file mode 100644
index 0000000000..7dd1979886
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Dropdown/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import Dropdown from 'components/GUIKit/Dropdown';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/DropdownTerms/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/DropdownTerms/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..0da839a8aa
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/DropdownTerms/__snapshots__/index.jsx.snap
@@ -0,0 +1,71 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+
+
+

+
+
+`;
diff --git a/__tests__/shared/components/GUIKit/DropdownTerms/index.jsx b/__tests__/shared/components/GUIKit/DropdownTerms/index.jsx
new file mode 100644
index 0000000000..1d526db680
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/DropdownTerms/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import DropdownTerms from 'components/GUIKit/DropdownTerms';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/RadioButton/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/RadioButton/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..29192cd7c7
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/RadioButton/__snapshots__/index.jsx.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+
+
+
+ radio
+
+
+
+`;
diff --git a/__tests__/shared/components/GUIKit/RadioButton/index.jsx b/__tests__/shared/components/GUIKit/RadioButton/index.jsx
new file mode 100644
index 0000000000..f386d2bbbd
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/RadioButton/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import RadioButton from 'components/GUIKit/RadioButton';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/TextInput/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/TextInput/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..f85a42e507
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/TextInput/__snapshots__/index.jsx.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+
+
+`;
diff --git a/__tests__/shared/components/GUIKit/TextInput/index.jsx b/__tests__/shared/components/GUIKit/TextInput/index.jsx
new file mode 100644
index 0000000000..fb49292a71
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/TextInput/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import TextInput from 'components/GUIKit/TextInput';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/Textarea/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Textarea/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..c1aa4a9108
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Textarea/__snapshots__/index.jsx.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+
+
+`;
diff --git a/__tests__/shared/components/GUIKit/Textarea/index.jsx b/__tests__/shared/components/GUIKit/Textarea/index.jsx
new file mode 100644
index 0000000000..e66da02350
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Textarea/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import Textarea from 'components/GUIKit/Textarea';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/Toggles/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Toggles/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..a48804cb5d
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Toggles/__snapshots__/index.jsx.snap
@@ -0,0 +1,16 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+`;
diff --git a/__tests__/shared/components/GUIKit/Toggles/index.jsx b/__tests__/shared/components/GUIKit/Toggles/index.jsx
new file mode 100644
index 0000000000..25a1a5b354
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Toggles/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import Toggles from 'components/GUIKit/Toggles';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(());
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/config/default.js b/config/default.js
index 7b9001cd62..65fc7b51bd 100644
--- a/config/default.js
+++ b/config/default.js
@@ -410,4 +410,7 @@ module.exports = {
POLICY_PAGES_PATH: '/policy',
GIGS_PAGES_PATH: '/gigs',
START_PAGE_PATH: '/start',
+ GUIKIT: {
+ DEBOUNCE_ON_CHANGE_TIME: 150,
+ },
};
diff --git a/src/assets/images/dropdown-arrow.png b/src/assets/images/dropdown-arrow.png
new file mode 100644
index 0000000000..40edda6752
Binary files /dev/null and b/src/assets/images/dropdown-arrow.png differ
diff --git a/src/shared/components/GUIKit/Assets/Images/icon-next.svg b/src/shared/components/GUIKit/Assets/Images/icon-next.svg
new file mode 100644
index 0000000000..cb096e3a01
--- /dev/null
+++ b/src/shared/components/GUIKit/Assets/Images/icon-next.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/src/shared/components/GUIKit/Assets/Images/icon-prev.svg b/src/shared/components/GUIKit/Assets/Images/icon-prev.svg
new file mode 100644
index 0000000000..6b3b226ad2
--- /dev/null
+++ b/src/shared/components/GUIKit/Assets/Images/icon-prev.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss
new file mode 100644
index 0000000000..b8c20ed703
--- /dev/null
+++ b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss
@@ -0,0 +1,82 @@
+// GUIKit colors
+$gui-kit-gray-30: #aaa;
+$gui-kit-gray-90: #2a2a2a;
+$gui-kit-level-2: #0ab88a;
+
+@mixin textInputLabel {
+ font-size: 12px;
+ line-height: 24px;
+ background-color: $tc-white;
+ position: absolute;
+ top: 0;
+ left: 8px;
+ padding: 0 8px;
+ color: $gui-kit-gray-30;
+ display: none;
+}
+
+@mixin textInput {
+ border: 1px solid $gui-kit-gray-30;
+ border-radius: 6px;
+ font-size: 16px;
+ padding: 15px;
+ margin-bottom: 0;
+ margin-top: 12px;
+
+ &::-webkit-input-placeholder {
+ /* Edge */
+ color: $gui-kit-gray-30;
+ opacity: 1;
+ text-transform: none;
+ font-size: 16px;
+ }
+
+ &:-ms-input-placeholder {
+ /* Internet Explorer 10-11 */
+ color: $gui-kit-gray-30;
+ opacity: 1;
+ text-transform: none;
+ font-size: 16px;
+ }
+
+ &::placeholder {
+ color: $gui-kit-gray-30;
+ opacity: 1;
+ text-transform: none;
+ font-size: 16px;
+ }
+
+ &:focus {
+ border: 1px solid $gui-kit-gray-30;
+ box-shadow: none;
+ }
+}
+
+@mixin textInputXs {
+ font-size: 14px;
+
+ &::-webkit-input-placeholder {
+ /* Edge */
+ font-size: 14px;
+ }
+
+ &:-ms-input-placeholder {
+ /* Internet Explorer 10-11 */
+ font-size: 14px;
+ }
+
+ &::placeholder {
+ font-size: 14px;
+ }
+}
+
+@mixin errorMessage {
+ font-size: 14px;
+ line-height: 20px;
+ margin-top: 10px;
+ margin-left: 15px;
+}
+
+@mixin errorMessageXs {
+ margin-top: 8px;
+}
diff --git a/src/shared/components/GUIKit/Assets/Styles/Includes/index.scss b/src/shared/components/GUIKit/Assets/Styles/Includes/index.scss
new file mode 100644
index 0000000000..7794723d66
--- /dev/null
+++ b/src/shared/components/GUIKit/Assets/Styles/Includes/index.scss
@@ -0,0 +1 @@
+@import 'mixin';
diff --git a/src/shared/components/GUIKit/Assets/Styles/_default.scss b/src/shared/components/GUIKit/Assets/Styles/_default.scss
new file mode 100644
index 0000000000..71f0c7cc35
--- /dev/null
+++ b/src/shared/components/GUIKit/Assets/Styles/_default.scss
@@ -0,0 +1,7 @@
+@import '~components/Contentful/default';
+@import '~components/buttons/themed/tc';
+@import './Includes';
+
+.container {
+ font-family: Roboto, sans-serif;
+}
diff --git a/src/shared/components/GUIKit/Checkbox/index.jsx b/src/shared/components/GUIKit/Checkbox/index.jsx
new file mode 100644
index 0000000000..0576ea93c7
--- /dev/null
+++ b/src/shared/components/GUIKit/Checkbox/index.jsx
@@ -0,0 +1,59 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/label-has-for */
+/**
+ * Checkbox component.
+ */
+import React, { useRef, useState } from 'react';
+import PT from 'prop-types';
+import IconCheckSolid from 'assets/images/dashboard/ico-checkmark.svg';
+import _ from 'lodash';
+import './style.scss';
+
+import { config } from 'topcoder-react-utils';
+
+function Checkbox({
+ checked,
+ onChange,
+ size,
+}) {
+ const [checkedInternal, setCheckedInternal] = useState(checked);
+ let sizeStyle = size === 'lg' ? 'lgSize' : null;
+ let checkmarkSize = size === 'lg' ? 16 : 0;
+ if (!sizeStyle) {
+ sizeStyle = size === 'xs' ? 'xsSize' : 'smSize';
+ checkmarkSize = size === 'xs' ? 10 : 13;
+ }
+ const delayedOnChange = useRef(
+ _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME),
+ ).current;
+
+ return (
+
+ );
+}
+
+Checkbox.defaultProps = {
+ checked: false,
+ onChange: () => {},
+ size: 'sm',
+};
+
+Checkbox.propTypes = {
+ checked: PT.bool,
+ onChange: PT.func,
+ size: PT.oneOf(['xs', 'sm', 'lg']),
+};
+
+export default Checkbox;
diff --git a/src/shared/components/GUIKit/Checkbox/style.scss b/src/shared/components/GUIKit/Checkbox/style.scss
new file mode 100644
index 0000000000..7d6801f888
--- /dev/null
+++ b/src/shared/components/GUIKit/Checkbox/style.scss
@@ -0,0 +1,109 @@
+@import '~components/GUIKit/Assets/Styles/default';
+
+/* Create a custom checkbox */
+.checkmark {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: $tc-white;
+ border: 1px solid $gui-kit-gray-30;
+
+ /* Create the checkmark/indicator (hidden when not checked) */
+ .after {
+ position: absolute;
+ display: none;
+ left: 50%;
+ top: 50%;
+ -webkit-filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35));
+ filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35));
+
+ :global {
+ path {
+ fill: $tc-white;
+ }
+ }
+ }
+}
+
+/* The container */
+.container {
+ display: block;
+ position: relative;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ // lg size
+ &.lgSize {
+ width: 25px;
+ height: 25px;
+
+ .checkmark {
+ width: 25px;
+ height: 25px;
+ border-radius: 4px;
+
+ .after {
+ margin-left: -8px;
+ margin-top: -9px;
+ }
+ }
+ }
+
+ // sm size
+ &.smSize {
+ width: 20px;
+ height: 20px;
+
+ .checkmark {
+ width: 20px;
+ height: 20px;
+ border-radius: 3px;
+
+ .after {
+ margin-left: -6px;
+ margin-top: -7px;
+ }
+ }
+ }
+
+ // xs size
+ &.xsSize {
+ width: 15px;
+ height: 15px;
+
+ .checkmark {
+ width: 15px;
+ height: 15px;
+ border-radius: 2px;
+
+ .after {
+ margin-left: -5px;
+ margin-top: -5px;
+ }
+ }
+ }
+
+ /* Hide the browser's default checkbox */
+ input {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ height: 0;
+ width: 0;
+
+ /* When the checkbox is checked, add a blue background */
+ &:checked ~ .checkmark {
+ background-color: $gui-kit-level-2;
+ border: none;
+ box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29);
+
+ /* Show the checkmark when checked */
+ .after {
+ display: block;
+ }
+ }
+ }
+}
diff --git a/src/shared/components/GUIKit/Datepicker/index.jsx b/src/shared/components/GUIKit/Datepicker/index.jsx
new file mode 100644
index 0000000000..53bc0887ae
--- /dev/null
+++ b/src/shared/components/GUIKit/Datepicker/index.jsx
@@ -0,0 +1,88 @@
+/* eslint-disable jsx-a11y/label-has-for */
+/**
+ * Datepicker component.
+ */
+import React, { useState } from 'react';
+import PT from 'prop-types';
+import moment from 'moment';
+import './style.scss';
+
+import 'react-dates/initialize';
+import { SingleDatePicker } from 'react-dates';
+import IconCalendar from 'assets/images/tc-edu/icon-calendar.svg';
+import useWindowSize from 'utils/useWindowSize';
+import IconNext from '../Assets/Images/icon-next.svg';
+import IconPrev from '../Assets/Images/icon-prev.svg';
+
+function Datepicker({
+ value,
+ placeholder,
+ label,
+ onChange,
+ errorMsg,
+ required,
+}) {
+ const [date, setDate] = useState(value ? moment(value) : null);
+ const [focused, setFocused] = useState(false);
+ const { width } = useWindowSize();
+ return (
+
+
}
+ date={date}
+ onDateChange={(changedDate) => {
+ setDate(changedDate);
+ onChange(changedDate ? changedDate.toDate() : null);
+ }}
+ focused={focused}
+ onFocusChange={({ focused: changedFocused }) => setFocused(changedFocused)
+ }
+ id={`${value}-${placeholder}-${label}-${errorMsg}-${required}`}
+ placeholder={`${placeholder}${placeholder && required ? ' *' : ''}`}
+ inputIconPosition="after"
+ numberOfMonths={1}
+ navPrev={
}
+ navNext={
}
+ displayFormat="MMM DD, YYYY"
+ daySize={width > 600 ? 53 : 35}
+ renderDayContents={d => (
{d.date ? d.date() : ''}
)}
+ enableOutsideDays
+ firstDayOfWeek={1}
+ weekDayFormat="ddd"
+ />
+ {label ? (
+
+ {label}
+ {required ? * : null}
+
+ ) : null}
+ {errorMsg ?
{errorMsg} : null}
+
+ );
+}
+
+Datepicker.defaultProps = {
+ value: null,
+ placeholder: '',
+ label: '',
+ onChange: () => {},
+ errorMsg: '',
+ required: false,
+};
+
+Datepicker.propTypes = {
+ value: PT.instanceOf(Date),
+ placeholder: PT.string,
+ label: PT.string,
+ onChange: PT.func,
+ errorMsg: PT.string,
+ required: PT.bool,
+};
+
+export default Datepicker;
diff --git a/src/shared/components/GUIKit/Datepicker/style.scss b/src/shared/components/GUIKit/Datepicker/style.scss
new file mode 100644
index 0000000000..8f27b70db5
--- /dev/null
+++ b/src/shared/components/GUIKit/Datepicker/style.scss
@@ -0,0 +1,203 @@
+@import '~components/GUIKit/Assets/Styles/default';
+
+.container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ .label {
+ @include textInputLabel;
+ }
+
+ &.haveValue .label,
+ &.isFocused .label {
+ display: flex;
+ }
+
+ &.isFocused .label {
+ color: $gui-kit-level-2;
+ }
+
+ &.haveError .label,
+ &.haveError.isFocused .label {
+ color: $tc-level-5;
+ }
+
+ :global {
+ @import '~react-dates/lib/css/_datepicker.css';
+
+ button {
+ &:focus {
+ outline: none;
+ }
+ }
+
+ .SingleDatePickerInput {
+ position: relative;
+
+ .SingleDatePickerInput_calendarIcon {
+ position: absolute;
+ top: 12px;
+ right: 0;
+ margin: 0;
+ padding: 0;
+ width: 48px;
+ bottom: 0;
+ z-index: 1;
+ }
+
+ .DateInput {
+ input {
+ @include textInput;
+
+ height: 52px;
+ padding-right: 48px;
+ }
+
+ .DateInput_fang {
+ display: none;
+ }
+ }
+
+ .SingleDatePicker_picker {
+ z-index: 7;
+ top: 64px !important;
+
+ .DayPicker__withBorder {
+ border: 1px solid $gui-kit-gray-30;
+ box-shadow: 2px 2px 3px 0 $tc-gray-neutral-light;
+ overflow: hidden;
+ }
+
+ .DayPickerNavigation_button {
+ position: absolute;
+ top: 18px;
+
+ &:focus {
+ outline: none;
+ }
+
+ svg {
+ path {
+ fill: $gui-kit-level-2;
+ }
+ }
+
+ &:first-child {
+ left: 18px;
+ }
+
+ &:last-child {
+ right: 18px;
+ }
+ }
+
+ .CalendarMonth_caption {
+ font-size: 16px;
+ line-height: 26px;
+ padding-top: 13px;
+ padding-bottom: 10px;
+ margin-bottom: 45px;
+ position: relative;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: -5px;
+ width: calc(100% + 10px);
+ height: 1px;
+ background-color: $tc-gray-20;
+ }
+ }
+
+ .DayPicker_weekHeader {
+ .DayPicker_weekHeader_ul {
+ .DayPicker_weekHeader_li {
+ small {
+ color: $gui-kit-gray-90;
+ font-weight: 500 !important;
+ }
+ }
+ }
+ }
+
+ .DayPicker_focusRegion {
+ .DayPicker_transitionContainer {
+ &.DayPicker_transitionContainer__horizontal {
+ transition: none !important;
+ }
+
+ .CalendarMonthGrid {
+ .CalendarMonthGrid_month__horizontal {
+ .CalendarMonth {
+ table.CalendarMonth_table {
+ tbody {
+ tr {
+ td {
+ border: none;
+ border-radius: 50%;
+ font-size: 16px;
+
+ &:focus {
+ outline: none;
+ }
+
+ div {
+ width: 35px;
+ height: 35px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: auto !important;
+ border-radius: 50%;
+ }
+
+ &.CalendarDay__selected {
+ background: transparent !important;
+
+ div {
+ background: $gui-kit-level-2;
+ }
+ }
+
+ &.CalendarDay__outside {
+ color: $tc-gray-20;
+ }
+
+ &:not(.CalendarDay__outside):not(.CalendarDay__blocked_out_of_range):not(.CalendarDay__selected):hover {
+ background: transparent !important;
+
+ div {
+ background: $tc-gray-05;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ &.haveError {
+ :global {
+ .SingleDatePickerInput {
+ .DateInput {
+ input {
+ border: 2px solid $tc-level-5;
+ }
+ }
+ }
+ }
+ }
+}
+
+.errorMessage {
+ @include errorMessage;
+}
diff --git a/src/shared/components/GUIKit/Dropdown/index.jsx b/src/shared/components/GUIKit/Dropdown/index.jsx
index 605f542276..082cba1d5c 100644
--- a/src/shared/components/GUIKit/Dropdown/index.jsx
+++ b/src/shared/components/GUIKit/Dropdown/index.jsx
@@ -2,53 +2,98 @@
/**
* Dropdown component.
*/
-import React from 'react';
+import React, { useState, useRef } from 'react';
import PT from 'prop-types';
+import _ from 'lodash';
import ReactSelect from 'react-select';
import './style.scss';
+import iconDown from 'assets/images/dropdown-arrow.png';
+import { config } from 'topcoder-react-utils';
+
function Dropdown({
options,
- selectedId,
- placeholder,
label,
+ required,
+ placeholder,
onChange,
+ errorMsg,
+ size,
}) {
+ const [internalOptions, setInternalOptions] = useState(options);
+ const selectedOption = _.find(internalOptions, { selected: true });
+ const [focused, setFocused] = useState(false);
+ const delayedOnChange = useRef(
+ _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME),
+ ).current;
+ const sizeStyle = size === 'lg' ? 'lgSize' : 'xsSize';
+
return (
-
-
-
(
-
- {option.name}
-
- )}
- />
+ setFocused(true)}
+ onBlurCapture={() => setFocused(false)}
+ className="dropdownContainer"
+ styleName={`container ${sizeStyle} ${selectedOption ? 'haveValue' : ''} ${
+ errorMsg ? 'haveError' : ''
+ } ${focused ? 'isFocused' : ''}`}
+ >
+
+
({ value: o.label, label: o.label }))}
+ value={selectedOption ? selectedOption.label : null}
+ onChange={(value) => {
+ if (value) {
+ const newOptions = internalOptions.map(o => ({
+ selected: value.label === o.label,
+ label: o.label,
+ }));
+ setInternalOptions(newOptions);
+ delayedOnChange(
+ _.cloneDeep(newOptions),
+ onChange,
+ );
+ }
+ }}
+ placeholder={`${placeholder}${placeholder && required ? ' *' : ''}`}
+ clearable={false}
+ />
+
+
+ {label ? (
+
+ {label}
+ {required ? * : null}
+
+ ) : null}
+ {errorMsg ? (
{errorMsg}) : null}
);
}
Dropdown.defaultProps = {
- options: [],
- selectedId: null,
placeholder: '',
label: '',
+ required: false,
+ onChange: () => {},
+ errorMsg: '',
+ size: 'lg',
};
Dropdown.propTypes = {
- options: PT.arrayOf(PT.shape),
- selectedId: PT.string,
+ options: PT.arrayOf(
+ PT.shape({
+ label: PT.string.isRequired,
+ selected: PT.bool.isRequired,
+ }),
+ ).isRequired,
placeholder: PT.string,
label: PT.string,
- onChange: PT.func.isRequired,
+ required: PT.bool,
+ onChange: PT.func,
+ errorMsg: PT.string,
+ size: PT.oneOf(['xs', 'lg']),
};
export default Dropdown;
diff --git a/src/shared/components/GUIKit/Dropdown/style.scss b/src/shared/components/GUIKit/Dropdown/style.scss
index 01dc2ec294..837b2fe5e5 100644
--- a/src/shared/components/GUIKit/Dropdown/style.scss
+++ b/src/shared/components/GUIKit/Dropdown/style.scss
@@ -1,27 +1,227 @@
+@import '~components/GUIKit/Assets/Styles/default';
+
+.label {
+ @include textInputLabel;
+}
+
+.relative {
+ position: relative;
+}
+
+.errorMessage {
+ @include errorMessage;
+}
+
+.iconDropdown {
+ position: absolute;
+ top: 50%;
+ right: 16px;
+ pointer-events: none;
+ margin-top: -4px;
+}
+
.container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ padding-top: 12px;
+
+ &.haveValue .label,
+ &.isFocused .label {
+ display: flex;
+ }
+
+ &.isFocused {
+ .label {
+ color: $gui-kit-level-2;
+ }
+
+ .iconDropdown {
+ transform: scale(1, -1);
+ }
+ }
+
+ &.haveError .label,
+ &.haveError.isFocused .label {
+ color: $tc-level-5;
+ }
+
:global {
@import '~react-select/dist/react-select';
width: 100%;
- input.Select-input,
- input.Select-input:focus {
- background-color: transparent !important;
- margin-left: 0 !important;
- padding-right: 6px !important;
- color: red;
+ .Select-control {
+ margin: 0;
+ padding: 0;
+ border: 1px solid $gui-kit-gray-30 !important;
+ border-radius: 6px !important;
+ height: 52px;
+ outline: none !important;
+ box-shadow: none !important;
+ }
+
+ .Select-input {
+ input {
+ font-size: 16px;
+ padding: 0;
+ height: 22px;
+ line-height: 22px;
+
+ &:focus {
+ border: none;
+ box-shadow: none;
+ }
+ }
+ }
+
+ .Select-value {
+ .Select-value-label {
+ height: 22px;
+ line-height: 22px;
+ font-size: 16px;
+ }
+ }
+
+ .Select-placeholder,
+ .Select-value,
+ .Select-input {
+ padding: 0 15px !important;
+ height: 100% !important;
+ display: flex !important;
+ align-items: center !important;
+ }
+
+ .Select-placeholder {
+ color: $gui-kit-gray-30;
+ opacity: 1;
+ text-transform: none;
+ font-size: 16px;
}
.Select-multi-value-wrapper {
width: 100% !important;
+ height: 100% !important;
+ }
+
+ .Select-arrow-zone {
+ padding-right: 15px !important;
+
+ .Select-arrow {
+ background-image: none;
+ border: none;
+ width: 15px;
+ height: 9px;
+ background-size: 15px 9px;
+ top: 0 !important;
+ border-width: 0 !important;
+ opacity: 0;
+ pointer-events: none;
+ }
+ }
+
+ .Select {
+ &.is-open {
+ .Select-arrow-zone {
+ .Select-arrow {
+ transform: scale(1, -1);
+ }
+ }
+ }
+ }
+
+ .Select-menu-outer {
+ top: calc(100% + 2px) !important;
+ border: 1px solid $gui-kit-gray-30 !important;
+ border-radius: 0 !important;
+ max-height: 269px;
+ z-index: 7;
+
+ .Select-menu {
+ max-height: 269px;
+
+ .Select-option {
+ padding: 0 15px !important;
+ font-size: 16px !important;
+ line-height: 30px !important;
+ color: $gui-kit-gray-90 !important;
+ background-color: transparent !important;
+
+ &.is-selected {
+ font-weight: bold !important;
+ }
+
+ &:hover {
+ background-color: #229173 !important;
+ color: $tc-white !important;
+ }
+ }
+ }
}
}
- .label {
- position: relative;
+ &.haveError {
+ :global {
+ .Select-control {
+ border: 2px solid $tc-level-5 !important;
+ }
+ }
}
- .active-option {
- color: red;
+ // lg size
+ &.lgSize {
+ :global {
+ .Select-control {
+ height: 52px;
+ }
+
+ .Select-input {
+ input {
+ height: 52px;
+ }
+ }
+ }
+ }
+
+ // xs size
+ &.xsSize {
+ :global {
+ .Select-control {
+ height: 40px;
+ }
+
+ .Select-input {
+ input {
+ font-size: 14px;
+ height: 40px;
+ }
+ }
+
+ .Select-value {
+ .Select-value-label {
+ font-size: 14px;
+ }
+ }
+
+ .Select-placeholder {
+ font-size: 14px;
+ }
+
+ .Select-menu-outer {
+ * {
+ font-size: 14px !important;
+ }
+
+ .Select-menu {
+ .Select-option {
+ font-size: 14px !important;
+ }
+ }
+ }
+ }
+
+ .errorMessage {
+ @include errorMessageXs;
+ }
}
}
diff --git a/src/shared/components/GUIKit/DropdownTerms/index.jsx b/src/shared/components/GUIKit/DropdownTerms/index.jsx
new file mode 100644
index 0000000000..0eb25fc8ce
--- /dev/null
+++ b/src/shared/components/GUIKit/DropdownTerms/index.jsx
@@ -0,0 +1,189 @@
+/* eslint-disable jsx-a11y/label-has-for */
+/**
+ * Dropdown terms component.
+ */
+import React, { useState, useRef, useEffect } from 'react';
+import PT from 'prop-types';
+import _ from 'lodash';
+import { Creatable } from 'react-select';
+import iconDown from 'assets/images/dropdown-arrow.png';
+import './style.scss';
+
+import { config } from 'topcoder-react-utils';
+
+function DropdownTerms({
+ terms,
+ placeholder,
+ label,
+ required,
+ onChange,
+ errorMsg,
+ addNewOptionPlaceholder,
+}) {
+ const [internalTerms, setInternalTerms] = useState(terms);
+ const selectedOption = _.filter(internalTerms, { selected: true }).map(o => ({
+ value: o.label,
+ label: o.label,
+ }));
+ const [focused, setFocused] = useState(false);
+ const delayedOnChange = useRef(
+ _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME),
+ ).current;
+ const containerRef = useRef(null);
+ let inputField;
+ useEffect(() => {
+ const selectInput = containerRef.current.getElementsByClassName('Select-input');
+ const selectMenuOuter = containerRef.current.getElementsByClassName('Select-menu-outer');
+ if (selectInput && selectInput.length) {
+ const selectControl = containerRef.current.getElementsByClassName(
+ 'Select-control',
+ );
+ const height1 = selectMenuOuter && selectMenuOuter.length
+ ? selectMenuOuter[0].offsetHeight
+ : 0;
+ const height2 = selectControl && selectControl.length
+ ? selectControl[0].offsetHeight
+ : 0;
+ selectInput[0].style.top = focused ? `${height1 + height2 - 1}px` : '0';
+ inputField = selectInput[0].getElementsByTagName('input');
+ inputField[0].placeholder = focused ? addNewOptionPlaceholder : '';
+ inputField[0].style.border = 'none';
+ inputField[0].style.boxShadow = 'none';
+ }
+ }, [focused, selectedOption]);
+
+ const CustomReactSelectRow = React.forwardRef(({
+ className,
+ option,
+ children,
+ onSelect,
+ }, ref) => (children ? (
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ onSelect(option, event);
+ }}
+ title={option.title}
+ tabIndex={-1}
+ >
+ {children}
+
+ ) : null));
+
+ CustomReactSelectRow.defaultProps = {
+ children: null,
+ className: '',
+ onSelect: () => {},
+ };
+
+ CustomReactSelectRow.propTypes = {
+ children: PT.node,
+ className: PT.string,
+ onSelect: PT.func,
+ option: PT.object.isRequired,
+ };
+
+ return (
+
+
+
setFocused(true)}
+ onClose={() => setFocused(false)}
+ autosize={false}
+ optionComponent={CustomReactSelectRow}
+ options={internalTerms
+ .map(o => ({ value: o.label, label: o.label }))
+ }
+ value={selectedOption}
+ onInputKeyDown={(e) => {
+ switch (e.keyCode) {
+ case 13: // ENTER
+ if (inputField && inputField && inputField[0]) {
+ const { value } = inputField[0];
+ if (
+ !value
+ || !value.trim()
+ || _.find(selectedOption, { label: value })
+ || _.find(internalTerms, { label: value })
+ ) {
+ e.preventDefault();
+ }
+ }
+ break;
+ default:
+ }
+ }}
+ onChange={(value) => {
+ const newValues = _.filter(
+ value,
+ o => o.label && o.label.trim() && !_.find(internalTerms, { label: o.label }),
+ ).map(o => ({ selected: true, label: o.label }));
+ const newTerms = internalTerms
+ .map(o => ({
+ selected: !!_.find(value, { label: o.label }),
+ label: o.label,
+ }))
+ .concat(newValues);
+ setInternalTerms(newTerms);
+ delayedOnChange(
+ _.cloneDeep(newTerms),
+ onChange,
+ );
+ }}
+ placeholder={focused ? '' : `${placeholder}${placeholder && required ? ' *' : ''}`}
+ clearable={false}
+ backspaceRemoves={false}
+ multi
+ promptTextCreator={() => null}
+ filterOptions={() => _.filter(
+ internalTerms, t => !_.find(selectedOption, { label: t.label }),
+ ).map(o => ({ value: o.label, label: o.label }))}
+ />
+
+
+ {label ? (
+
+ {label}
+ {required ? * : null}
+
+ ) : null}
+ {errorMsg ?
{errorMsg} : null}
+
+ );
+}
+
+DropdownTerms.defaultProps = {
+ placeholder: '',
+ label: '',
+ required: false,
+ onChange: () => {},
+ errorMsg: '',
+ addNewOptionPlaceholder: '',
+};
+
+DropdownTerms.propTypes = {
+ terms: PT.arrayOf(
+ PT.shape({
+ label: PT.string.isRequired,
+ selected: PT.bool.isRequired,
+ }),
+ ).isRequired,
+ placeholder: PT.string,
+ label: PT.string,
+ required: PT.bool,
+ onChange: PT.func,
+ errorMsg: PT.string,
+ addNewOptionPlaceholder: PT.string,
+};
+
+export default DropdownTerms;
diff --git a/src/shared/components/GUIKit/DropdownTerms/style.scss b/src/shared/components/GUIKit/DropdownTerms/style.scss
new file mode 100644
index 0000000000..121aa54da7
--- /dev/null
+++ b/src/shared/components/GUIKit/DropdownTerms/style.scss
@@ -0,0 +1,163 @@
+@import '../Dropdown/style.scss';
+
+.label {
+ z-index: 6;
+}
+
+.relative {
+ position: relative;
+}
+
+.errorMessage,
+.haveValue,
+.haveError,
+.iconDropdown,
+.isFocused {
+ font-family: Roboto, sans-serif;
+}
+
+.container {
+ :global {
+ .Select-control {
+ min-height: 52px;
+ height: auto;
+ display: flex !important;
+ overflow: visible !important;
+ }
+
+ .Select-arrow-zone {
+ flex-shrink: 0 !important;
+ display: flex !important;
+ margin: auto 0 auto 10px !important;
+ width: 30px !important;
+ }
+
+ .Select-multi-value-wrapper {
+ display: flex !important;
+ flex-wrap: wrap;
+ padding-top: 12px !important;
+ padding-left: 8px !important;
+ max-height: 104px;
+ overflow: auto;
+ }
+
+ .Select-value {
+ border: none !important;
+ background-color: $tc-gray-05 !important;
+ height: 25px !important;
+ display: flex !important;
+ flex-direction: row-reverse !important;
+ align-items: center;
+ padding: 0 !important;
+ border-radius: 5px !important;
+ font-size: 12 !important;
+ color: $gui-kit-gray-90 !important;
+ padding-left: 7px !important;
+ margin-top: 0 !important;
+ margin-bottom: 5px !important;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ z-index: 6;
+
+ .Select-value-icon {
+ border-right: none !important;
+ padding: 0 7px !important;
+ margin: 0 !important;
+ color: $gui-kit-gray-90 !important;
+ background: transparent !important;
+ }
+
+ .Select-value-label {
+ padding: 0 !important;
+ margin: 0 !important;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: $gui-kit-gray-90 !important;
+ text-decoration: none !important;
+ font-size: 12px;
+ }
+ }
+
+ .Select-input {
+ position: absolute;
+ left: 0;
+ right: 0;
+ background: transparent;
+ height: 100% !important;
+ z-index: 5;
+ margin: 0 !important;
+ cursor: pointer;
+
+ input {
+ height: 25px !important;
+ opacity: 0;
+ cursor: pointer;
+ }
+ }
+
+ .Select-option {
+ border-bottom-right-radius: 0 !important;
+ border-bottom-left-radius: 0 !important;
+ }
+ }
+
+ &.isFocused {
+ :global {
+ .Select-input {
+ background: $tc-white;
+ height: 40px !important;
+ border: 1px solid $gui-kit-gray-30;
+ right: -1px;
+ left: -1px;
+ cursor: auto;
+ z-index: 7;
+
+ input {
+ opacity: 1;
+ cursor: auto;
+
+ &::-webkit-input-placeholder {
+ /* Edge */
+ color: $gui-kit-gray-30;
+ opacity: 1;
+ text-transform: none;
+ font-size: 16px;
+ }
+
+ &:-ms-input-placeholder {
+ /* Internet Explorer 10-11 */
+ color: $gui-kit-gray-30;
+ opacity: 1;
+ text-transform: none;
+ font-size: 16px;
+ }
+
+ &::placeholder {
+ color: $gui-kit-gray-30;
+ opacity: 1;
+ text-transform: none;
+ font-size: 16px;
+ }
+ }
+ }
+ }
+ }
+
+ &.isEmptySelectList {
+ :global {
+ .Select-menu-outer {
+ border: none !important;
+ }
+ }
+ }
+}
+
+.addAnotherSkill {
+ height: 40px;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
diff --git a/src/shared/components/GUIKit/JobListCard/style.scss b/src/shared/components/GUIKit/JobListCard/style.scss
index 308d760bb1..1252ab2fd4 100644
--- a/src/shared/components/GUIKit/JobListCard/style.scss
+++ b/src/shared/components/GUIKit/JobListCard/style.scss
@@ -1,4 +1,4 @@
-@import "~components/GUIKit/default";
+@import "~components/GUIKit/Assets/Styles/default";
@import "~components/Contentful/default";
.container {
diff --git a/src/shared/components/GUIKit/Paginate/style.scss b/src/shared/components/GUIKit/Paginate/style.scss
index 70a82393b3..f811e30912 100644
--- a/src/shared/components/GUIKit/Paginate/style.scss
+++ b/src/shared/components/GUIKit/Paginate/style.scss
@@ -1,4 +1,4 @@
-@import "~components/GUIKit/default";
+@import "~components/GUIKit/Assets/Styles/default";
.container {
display: flex;
diff --git a/src/shared/components/GUIKit/RadioButton/index.jsx b/src/shared/components/GUIKit/RadioButton/index.jsx
new file mode 100644
index 0000000000..fb2df581fb
--- /dev/null
+++ b/src/shared/components/GUIKit/RadioButton/index.jsx
@@ -0,0 +1,66 @@
+/* eslint-disable jsx-a11y/label-has-for */
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/**
+ * Radio button component.
+ */
+import React, { useRef, useState } from 'react';
+import PT from 'prop-types';
+import _ from 'lodash';
+import './style.scss';
+
+import { config } from 'topcoder-react-utils';
+
+function RadioButton({ options, onChange, size }) {
+ const [internalOptions, setInternalOptions] = useState(options);
+ const optionsWithKey = internalOptions.map((o, oIndex) => ({ ...o, key: oIndex }));
+ let sizeStyle = size === 'lg' ? 'lgSize' : null;
+ if (!sizeStyle) {
+ sizeStyle = size === 'xs' ? 'xsSize' : 'smSize';
+ }
+ const delayedOnChange = useRef(
+ _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME),
+ ).current;
+
+ return (
+
+ {optionsWithKey.map(o => (
+
+
+ {o.label ? ({o.label}) : null}
+
+ ))}
+
+ );
+}
+
+RadioButton.defaultProps = {
+ onChange: () => {},
+ size: 'sm',
+};
+
+RadioButton.propTypes = {
+ options: PT.arrayOf(
+ PT.shape({
+ label: PT.string,
+ value: PT.bool.isRequired,
+ }),
+ ).isRequired,
+ onChange: PT.func,
+ size: PT.oneOf(['xs', 'sm', 'lg']),
+};
+
+export default RadioButton;
diff --git a/src/shared/components/GUIKit/RadioButton/style.scss b/src/shared/components/GUIKit/RadioButton/style.scss
new file mode 100644
index 0000000000..63705c163d
--- /dev/null
+++ b/src/shared/components/GUIKit/RadioButton/style.scss
@@ -0,0 +1,147 @@
+@import '~components/GUIKit/Assets/Styles/default';
+
+/* Create a custom radio button */
+.checkmark {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: $tc-white;
+ border-radius: 50%;
+ border: 1px solid $gui-kit-gray-30;
+
+ /* Create the indicator (the dot/circle - hidden when not checked) */
+ &::after {
+ content: '';
+ position: absolute;
+ display: none;
+ top: 50%;
+ left: 50%;
+ margin-top: -6px;
+ margin-left: -6px;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background-color: $tc-white;
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.35);
+ }
+}
+
+.radioButton {
+ display: flex;
+ align-items: center;
+}
+
+.label {
+ font-size: 14px;
+}
+
+/* The container */
+.container {
+ display: block;
+ position: relative;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ color: $gui-kit-gray-90;
+
+ /* Hide the browser's default radio button */
+ input {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+
+ /* When the radio button is checked, add a blue background */
+ &:checked ~ .checkmark {
+ background-color: $gui-kit-level-2;
+ box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29);
+ border: none;
+
+ /* Show the indicator (dot/circle) when checked */
+ &::after {
+ display: block;
+ }
+ }
+ }
+}
+
+.radioButtonContainer {
+ display: flex;
+ flex-direction: column;
+
+ // lg size
+ &.lgSize {
+ .container {
+ padding-left: 24px;
+ line-height: 24px;
+ height: 24px;
+
+ .checkmark {
+ height: 24px;
+ width: 24px;
+
+ &::after {
+ margin-top: -6px;
+ margin-left: -6px;
+ width: 12px;
+ height: 12px;
+ }
+ }
+ }
+
+ .label {
+ margin-left: 15px;
+ }
+ }
+
+ // sm size
+ &.smSize {
+ .container {
+ padding-left: 20px;
+ line-height: 20px;
+ height: 20px;
+
+ .checkmark {
+ height: 20px;
+ width: 20px;
+
+ &::after {
+ margin-top: -5px;
+ margin-left: -5px;
+ width: 10px;
+ height: 10px;
+ }
+ }
+ }
+
+ .label {
+ margin-left: 10px;
+ }
+ }
+
+ // xs size
+ &.xsSize {
+ .container {
+ padding-left: 15px;
+ line-height: 15px;
+ height: 15px;
+
+ .checkmark {
+ height: 16px;
+ width: 16px;
+
+ &::after {
+ margin-top: -4px;
+ margin-left: -4px;
+ width: 8px;
+ height: 8px;
+ }
+ }
+ }
+
+ .label {
+ margin-left: 10px;
+ }
+ }
+}
diff --git a/src/shared/components/GUIKit/SearchCombo/style.scss b/src/shared/components/GUIKit/SearchCombo/style.scss
index 30d0928547..ce03a3219c 100644
--- a/src/shared/components/GUIKit/SearchCombo/style.scss
+++ b/src/shared/components/GUIKit/SearchCombo/style.scss
@@ -1,4 +1,4 @@
-@import "~components/GUIKit/default";
+@import "~components/GUIKit/Assets/Styles/default";
.container {
display: flex;
@@ -30,12 +30,9 @@
z-index: 0;
top: 8px;
left: 15px;
-
- @include xs-to-sm {
- max-width: 250px;
- overflow: hidden;
- white-space: nowrap;
- }
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: calc(100% - 20px);
}
.clear-search {
diff --git a/src/shared/components/GUIKit/TextInput/index.jsx b/src/shared/components/GUIKit/TextInput/index.jsx
new file mode 100644
index 0000000000..95a86dd516
--- /dev/null
+++ b/src/shared/components/GUIKit/TextInput/index.jsx
@@ -0,0 +1,69 @@
+/* eslint-disable jsx-a11y/label-has-for */
+/**
+ * Text input component.
+ */
+import React, { useState, useRef } from 'react';
+import PT from 'prop-types';
+import _ from 'lodash';
+import './style.scss';
+
+import { config } from 'topcoder-react-utils';
+
+function TextInput({
+ placeholder,
+ label,
+ errorMsg,
+ value,
+ onChange,
+ required,
+ size,
+}) {
+ const [val, setVal] = useState(value);
+ const delayedOnChange = useRef(
+ _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME),
+ ).current;
+ const sizeStyle = size === 'lg' ? 'lgSize' : 'xsSize';
+
+ return (
+
+ {
+ delayedOnChange(e.target.value, onChange);
+ setVal(e.target.value);
+ }}
+ />
+ {label ? (
+
+ ) : null}
+ {errorMsg ? ({errorMsg}) : null}
+
+ );
+}
+
+TextInput.defaultProps = {
+ placeholder: '',
+ label: '',
+ errorMsg: '',
+ value: '',
+ onChange: () => {},
+ required: false,
+ size: 'lg',
+};
+
+TextInput.propTypes = {
+ placeholder: PT.string,
+ label: PT.string,
+ errorMsg: PT.string,
+ value: PT.string,
+ onChange: PT.func,
+ required: PT.bool,
+ size: PT.oneOf(['xs', 'lg']),
+};
+
+export default TextInput;
diff --git a/src/shared/components/GUIKit/TextInput/style.scss b/src/shared/components/GUIKit/TextInput/style.scss
new file mode 100644
index 0000000000..b71c2b8184
--- /dev/null
+++ b/src/shared/components/GUIKit/TextInput/style.scss
@@ -0,0 +1,63 @@
+@import '~components/GUIKit/Assets/Styles/default';
+
+.errorMessage {
+ @include errorMessage;
+}
+
+.container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ label {
+ @include textInputLabel;
+ }
+
+ input:not([type='checkbox']) {
+ @include textInput;
+
+ height: 52px;
+ }
+
+ // lg size
+ &.lgSize {
+ input:not([type='checkbox']) {
+ height: 52px;
+ }
+ }
+
+ // xs size
+ &.xsSize {
+ input:not([type='checkbox']) {
+ @include textInputXs;
+
+ height: 40px;
+ padding: 0 15px;
+ }
+
+ .errorMessage {
+ @include errorMessageXs;
+ }
+ }
+
+ input:not([type='checkbox']).haveValue + label,
+ input:not([type='checkbox']):focus + label {
+ display: flex;
+ }
+
+ input:not([type='checkbox']):focus + label {
+ color: $gui-kit-level-2;
+ }
+
+ input:not([type='checkbox']).haveError + label,
+ input:not([type='checkbox']).haveError:focus + label {
+ color: $tc-level-5;
+ }
+
+ input:not([type='checkbox']).haveError,
+ input:not([type='checkbox']).haveError:active,
+ input:not([type='checkbox']).haveError:focus,
+ input:not([type='checkbox']).haveError:hover {
+ border: 2px solid $tc-level-5;
+ }
+}
diff --git a/src/shared/components/GUIKit/Textarea/index.jsx b/src/shared/components/GUIKit/Textarea/index.jsx
new file mode 100644
index 0000000000..f78ac4d75b
--- /dev/null
+++ b/src/shared/components/GUIKit/Textarea/index.jsx
@@ -0,0 +1,68 @@
+/* eslint-disable jsx-a11y/label-has-for */
+/**
+ * Textarea component.
+ */
+import React, { useState, useRef } from 'react';
+import PT from 'prop-types';
+import _ from 'lodash';
+import './style.scss';
+
+import { config } from 'topcoder-react-utils';
+
+function Textarea({
+ placeholder,
+ label,
+ errorMsg,
+ value,
+ onChange,
+ required,
+}) {
+ const [val, setVal] = useState(value);
+ const delayedOnChange = useRef(
+ _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME),
+ ).current;
+
+ return (
+
+ );
+}
+
+Textarea.defaultProps = {
+ placeholder: '',
+ label: '',
+ errorMsg: '',
+ value: '',
+ onChange: () => {},
+ required: false,
+};
+
+Textarea.propTypes = {
+ placeholder: PT.string,
+ label: PT.string,
+ errorMsg: PT.string,
+ value: PT.string,
+ onChange: PT.func,
+ required: PT.bool,
+};
+
+export default Textarea;
diff --git a/src/shared/components/GUIKit/Textarea/style.scss b/src/shared/components/GUIKit/Textarea/style.scss
new file mode 100644
index 0000000000..57f67b42a6
--- /dev/null
+++ b/src/shared/components/GUIKit/Textarea/style.scss
@@ -0,0 +1,66 @@
+@import '~components/GUIKit/Assets/Styles/default';
+
+.labelMask {
+ position: absolute;
+ background: $tc-white;
+ height: 11px;
+ top: 13px;
+ left: 4px;
+ right: 10px;
+}
+
+.container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ label {
+ @include textInputLabel;
+ }
+
+ textarea {
+ &.haveValue ~ label,
+ &:focus ~ label {
+ display: flex;
+ }
+
+ &:focus ~ label {
+ color: $gui-kit-level-2;
+ }
+
+ &.haveError ~ label,
+ &.haveError:focus ~ label {
+ color: $tc-level-5;
+ }
+ }
+
+ textarea,
+ textarea:active,
+ textarea:focus,
+ textarea:hover {
+ @include textInput;
+
+ min-height: 52px;
+ resize: none;
+ outline: none;
+ box-shadow: none;
+ height: 100%;
+ line-height: 26px;
+ }
+
+ textarea.haveError,
+ textarea.haveError:active,
+ textarea.haveError:focus,
+ textarea.haveError:hover {
+ border: 2px solid $tc-level-5;
+ }
+
+ textarea.haveError ~ .labelMask {
+ height: 10px;
+ top: 14px;
+ }
+}
+
+.errorMessage {
+ @include errorMessage;
+}
diff --git a/src/shared/components/GUIKit/Toggles/index.jsx b/src/shared/components/GUIKit/Toggles/index.jsx
new file mode 100644
index 0000000000..a9f48b4d4e
--- /dev/null
+++ b/src/shared/components/GUIKit/Toggles/index.jsx
@@ -0,0 +1,53 @@
+/* eslint-disable jsx-a11y/label-has-for */
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/**
+ * Toggles component.
+ */
+import React, { useRef, useState } from 'react';
+import PT from 'prop-types';
+import _ from 'lodash';
+import './style.scss';
+
+import { config } from 'topcoder-react-utils';
+
+function Toggles({ checked, onChange, size }) {
+ const [internalChecked, setInternalChecked] = useState(checked);
+ let sizeStyle = size === 'lg' ? 'lgSize' : null;
+ const delayedOnChange = useRef(
+ _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME),
+ );
+
+ if (!sizeStyle) {
+ sizeStyle = size === 'xs' ? 'xsSize' : 'smSize';
+ }
+
+ return (
+
+ );
+}
+
+Toggles.defaultProps = {
+ checked: false,
+ onChange: () => {},
+ size: 'sm',
+};
+
+Toggles.propTypes = {
+ checked: PT.bool,
+ onChange: PT.func,
+ size: PT.oneOf(['xs', 'sm', 'lg']),
+};
+
+export default Toggles;
diff --git a/src/shared/components/GUIKit/Toggles/style.scss b/src/shared/components/GUIKit/Toggles/style.scss
new file mode 100644
index 0000000000..ca91b38f88
--- /dev/null
+++ b/src/shared/components/GUIKit/Toggles/style.scss
@@ -0,0 +1,141 @@
+@import '~components/GUIKit/Assets/Styles/default';
+
+.container {
+ position: relative;
+ display: flex;
+ cursor: pointer;
+
+ .slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: $gui-kit-gray-30;
+ box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29);
+
+ &::before {
+ position: absolute;
+ content: '';
+ background-color: $tc-white;
+ box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.35);
+ border-radius: 50%;
+ }
+ }
+
+ // sm size
+ &.smSize {
+ width: 34px;
+ height: 20px;
+
+ .slider {
+ border-radius: 10px;
+
+ &::before {
+ height: 14px;
+ width: 14px;
+ left: 3px;
+ bottom: 3px;
+ }
+ }
+ }
+
+ // xs size
+ &.xsSize {
+ width: 26px;
+ height: 15px;
+
+ .slider {
+ border-radius: 7px;
+
+ &::before {
+ height: 11px;
+ width: 11px;
+ left: 2px;
+ bottom: 2px;
+ }
+ }
+ }
+
+ // lg size
+ &.lgSize {
+ width: 48px;
+ height: 25px;
+
+ .slider {
+ border-radius: 17px;
+
+ &::before {
+ height: 17px;
+ width: 17px;
+ left: 4px;
+ bottom: 4px;
+ }
+ }
+ }
+
+ input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+
+ &:focus + .slider {
+ box-shadow: 0 0 1px $gui-kit-level-2;
+ }
+
+ &:checked + .slider {
+ background-color: $gui-kit-level-2;
+ }
+ }
+
+ // lg size
+ &.lgSize input {
+ &:checked + .slider {
+ &::before {
+ -webkit-transform: translateX(23px);
+ -ms-transform: translateX(23px);
+ transform: translateX(23px);
+ }
+ }
+ }
+
+ // sm size
+ &.smSize input {
+ &:checked + .slider {
+ &::before {
+ -webkit-transform: translateX(14px);
+ -ms-transform: translateX(14px);
+ transform: translateX(14px);
+ }
+ }
+ }
+
+ // xs size
+ &.xsSize input {
+ &:checked + .slider {
+ &::before {
+ -webkit-transform: translateX(11px);
+ -ms-transform: translateX(11px);
+ transform: translateX(11px);
+ }
+ }
+ }
+}
+
+.on,
+.off {
+ position: absolute;
+ top: 5px;
+ font-size: 12px;
+ line-height: 14px;
+ color: $tc-white;
+}
+
+.on {
+ left: 8px;
+}
+
+.off {
+ right: 8px;
+}
diff --git a/src/shared/components/GUIKit/_default.scss b/src/shared/components/GUIKit/_default.scss
deleted file mode 100644
index b88b18f6e3..0000000000
--- a/src/shared/components/GUIKit/_default.scss
+++ /dev/null
@@ -1,2 +0,0 @@
-@import "~components/Contentful/default";
-@import "~components/buttons/themed/tc";
diff --git a/src/shared/components/examples/GUIKit/Checkbox/index.jsx b/src/shared/components/examples/GUIKit/Checkbox/index.jsx
new file mode 100644
index 0000000000..2a0d7d4066
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Checkbox/index.jsx
@@ -0,0 +1,189 @@
+import React from 'react';
+import Checkbox from 'components/GUIKit/Checkbox';
+
+import './style.scss';
+
+export default function CheckboxExample() {
+ const checkValues = [
+ {
+ label: '',
+ key: 1,
+ checked: false,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 2,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 3,
+ checked: false,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 4,
+ checked: true,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 5,
+ checked: false,
+ size: 'xs',
+ },
+ {
+ label: '',
+ key: 6,
+ checked: true,
+ size: 'xs',
+ },
+ {
+ label: 'Option 1',
+ key: 7,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: 'Option 1',
+ key: 8,
+ checked: true,
+ size: 'sm',
+ },
+ {
+ label: 'Option 1',
+ key: 9,
+ checked: true,
+ size: 'xs',
+ },
+ {
+ label: 'Option 2',
+ key: 10,
+ checked: false,
+ size: 'lg',
+ },
+ {
+ label: 'Option 2',
+ key: 11,
+ checked: false,
+ size: 'sm',
+ },
+ {
+ label: 'Option 2',
+ key: 12,
+ checked: false,
+ size: 'xs',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+
+
+
+ onChange(checked)}
+ size={checkValues[0].size}
+ checked={checkValues[0].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[1].size}
+ checked={checkValues[1].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[2].size}
+ checked={checkValues[2].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[3].size}
+ checked={checkValues[3].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[4].size}
+ checked={checkValues[4].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[5].size}
+ checked={checkValues[5].checked}
+ />
+
+
+
+
Examples usage
+
+
+
+ onChange(checked)}
+ size={checkValues[6].size}
+ checked={checkValues[6].checked}
+ />
+ {checkValues[6].label}
+
+
+ onChange(checked)}
+ size={checkValues[7].size}
+ checked={checkValues[7].checked}
+ />
+ {checkValues[7].label}
+
+
+ onChange(checked)}
+ size={checkValues[8].size}
+ checked={checkValues[8].checked}
+ />
+ {checkValues[8].label}
+
+
+
+
+ onChange(checked)}
+ size={checkValues[9].size}
+ checked={checkValues[9].checked}
+ />
+ {checkValues[9].label}
+
+
+ onChange(checked)}
+ size={checkValues[10].size}
+ checked={checkValues[10].checked}
+ />
+ {checkValues[10].label}
+
+
+ onChange(checked)}
+ size={checkValues[11].size}
+ checked={checkValues[11].checked}
+ />
+ {checkValues[11].label}
+
+
+
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Checkbox/style.scss b/src/shared/components/examples/GUIKit/Checkbox/style.scss
new file mode 100644
index 0000000000..06164ce657
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Checkbox/style.scss
@@ -0,0 +1,56 @@
+@import '~styles/mixins';
+
+.container {
+ display: flex;
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+}
+
+.grid1,
+.grid2,
+.grid21 {
+ display: flex;
+ flex-direction: column;
+}
+
+.grid11,
+.grid211,
+.grid2111 {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.grid111 {
+ display: flex;
+ width: 50px;
+}
+
+.grid11 {
+ margin-top: 20px;
+}
+
+.grid1 {
+ margin-right: 100px;
+}
+
+.grid2 {
+ margin-top: 20px;
+}
+
+.grid2Label {
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-bottom: 20px;
+}
+
+.grid2111 {
+ margin-right: 60px;
+ margin-bottom: 10px;
+
+ span {
+ margin-left: 15px;
+ font-size: 14px;
+ }
+}
diff --git a/src/shared/components/examples/GUIKit/Datepicker/index.jsx b/src/shared/components/examples/GUIKit/Datepicker/index.jsx
new file mode 100644
index 0000000000..b7d0309f1f
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Datepicker/index.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import Datepicker from 'components/GUIKit/Datepicker';
+
+import './style.scss';
+
+export default function DatepickerExample() {
+ const values = [
+ {
+ key: 2,
+ placeholder: 'Available From',
+ label: '',
+ value: null,
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 3,
+ placeholder: 'Available From',
+ label: 'Available From',
+ value: new Date(),
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ placeholder: 'Available From',
+ label: 'Available From',
+ value: new Date(),
+ required: false,
+ errorMsg: 'This date is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 5,
+ placeholder: 'Available From',
+ label: 'Available From',
+ value: new Date(),
+ required: true,
+ errorMsg: '',
+ sectionTitle: 'Require',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+
+
+ ))}
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Datepicker/style.scss b/src/shared/components/examples/GUIKit/Datepicker/style.scss
new file mode 100644
index 0000000000..7bb011b166
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Datepicker/style.scss
@@ -0,0 +1,31 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .datepickerContainer {
+ flex-grow: 1;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ flex-shrink: 0;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/Dropdown/index.jsx b/src/shared/components/examples/GUIKit/Dropdown/index.jsx
new file mode 100644
index 0000000000..2eb0b7d937
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Dropdown/index.jsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import Dropdown from 'components/GUIKit/Dropdown';
+import _ from 'lodash';
+import PT from 'prop-types';
+
+import './style.scss';
+
+function DropdownExample({ size }) {
+ const options = [
+ { label: 'Afghanistan', selected: false },
+ { label: 'Albania', selected: true },
+ { label: 'Andorra', selected: false },
+ { label: 'Anguilla', selected: false },
+ { label: 'Belgium', selected: false },
+ { label: 'Brazil', selected: false },
+ ];
+
+ const values = [
+ {
+ key: 1,
+ options: _.cloneDeep(options.map(o => ({ ...o, selected: false }))),
+ label: 'Country',
+ required: false,
+ placeholder: 'Select country',
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 2,
+ options: _.cloneDeep(options),
+ label: 'Country',
+ required: false,
+ placeholder: '',
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ options: _.cloneDeep(options),
+ label: 'Country',
+ required: false,
+ placeholder: '',
+ errorMsg: 'The country is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 3,
+ options: _.cloneDeep(options),
+ label: 'Country',
+ required: true,
+ placeholder: '',
+ errorMsg: '',
+ sectionTitle: 'Require',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+ onChange(changedOptions)}
+ options={value.options}
+ label={value.label}
+ required={value.required}
+ placeholder={value.placeholder}
+ errorMsg={value.errorMsg}
+ size={size}
+ />
+
+ ))}
+
+ );
+}
+
+DropdownExample.defaultProps = {
+ size: 'lg',
+};
+
+DropdownExample.propTypes = {
+ size: PT.oneOf(['xs', 'lg']),
+};
+
+export default DropdownExample;
diff --git a/src/shared/components/examples/GUIKit/Dropdown/style.scss b/src/shared/components/examples/GUIKit/Dropdown/style.scss
new file mode 100644
index 0000000000..fb6a4b5201
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Dropdown/style.scss
@@ -0,0 +1,33 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .dropdownContainer {
+ flex-grow: 1;
+ width: 0;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/DropdownTerms/index.jsx b/src/shared/components/examples/GUIKit/DropdownTerms/index.jsx
new file mode 100644
index 0000000000..110454360b
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/DropdownTerms/index.jsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import DropdownTerms from 'components/GUIKit/DropdownTerms';
+import _ from 'lodash';
+
+import './style.scss';
+
+export default function DropdownTermsExample() {
+ const options = [
+ { label: 'EJB', selected: false },
+ { label: 'Java', selected: true },
+ { label: 'Javascript', selected: false },
+ { label: 'MangoDB', selected: false },
+ { label: 'Oracle', selected: false },
+ { label: 'React', selected: false },
+ { label: 'Serverlet', selected: false },
+ { label: 'Web Services', selected: false },
+ { label: 'Zipkin', selected: false },
+ ];
+
+ const values = [
+ {
+ key: 1,
+ options: _.cloneDeep(options.map(o => ({ ...o, selected: false }))),
+ label: 'Tech Skills',
+ required: false,
+ placeholder: 'Tech Skills',
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ {
+ key: 2,
+ options: _.cloneDeep(options),
+ label: 'Tech Skills',
+ required: false,
+ placeholder: 'Tech Skills',
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ {
+ key: 4,
+ options: _.cloneDeep(options),
+ label: 'Tech Skills',
+ required: false,
+ placeholder: 'Tech Skills',
+ errorMsg: 'The skill is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ {
+ key: 5,
+ options: _.cloneDeep(options.map(o => ({ ...o, selected: false }))),
+ label: 'Tech Skills',
+ required: true,
+ placeholder: 'Tech Skills',
+ errorMsg: '',
+ sectionTitle: 'Require',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+ onChange(changedOptions)}
+ terms={value.options}
+ label={value.label}
+ required={value.required}
+ placeholder={value.placeholder}
+ errorMsg={value.errorMsg}
+ addNewOptionPlaceholder={value.addNewOptionPlaceholder}
+ />
+
+ ))}
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/DropdownTerms/style.scss b/src/shared/components/examples/GUIKit/DropdownTerms/style.scss
new file mode 100644
index 0000000000..cbda0d481c
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/DropdownTerms/style.scss
@@ -0,0 +1,33 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .dropdownContainer {
+ flex-grow: 1;
+ width: 0;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 970px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/RadioButton/index.jsx b/src/shared/components/examples/GUIKit/RadioButton/index.jsx
new file mode 100644
index 0000000000..06906851de
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/RadioButton/index.jsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import RadioButton from 'components/GUIKit/RadioButton';
+
+import './style.scss';
+
+export default function RadioButtonExample() {
+ const values = [
+ {
+ key: 1,
+ options: [{ label: '', value: false }, { label: '', value: true }],
+ size: 'lg',
+ },
+ {
+ key: 3,
+ options: [{ label: '', value: false }, { label: '', value: true }],
+ size: 'sm',
+ },
+ {
+ key: 5,
+ options: [{ label: '', value: false }, { label: '', value: true }],
+ size: 'xs',
+ },
+ {
+ key: 7,
+ options: [{ label: 'Option 1', value: true }, { label: 'Option 2', value: false }],
+ size: 'lg',
+ },
+ {
+ key: 8,
+ options: [{ label: 'Option 1', value: true }, { label: 'Option 2', value: false }],
+ size: 'sm',
+ },
+ {
+ key: 9,
+ options: [{ label: 'Option 1', value: true }, { label: 'Option 2', value: false }],
+ size: 'xs',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+
+ onChange(options)}
+ size={values[0].size}
+ options={values[0].options}
+ />
+ onChange(options)}
+ size={values[1].size}
+ options={values[1].options}
+ />
+ onChange(options)}
+ size={values[2].size}
+ options={values[2].options}
+ />
+
+
+
Examples usage
+
+ onChange(options)}
+ size={values[3].size}
+ options={values[3].options}
+ />
+ onChange(options)}
+ size={values[4].size}
+ options={values[4].options}
+ />
+ onChange(options)}
+ size={values[5].size}
+ options={values[5].options}
+ />
+
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/RadioButton/style.scss b/src/shared/components/examples/GUIKit/RadioButton/style.scss
new file mode 100644
index 0000000000..6775400632
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/RadioButton/style.scss
@@ -0,0 +1,59 @@
+@import '~styles/mixins';
+
+.container {
+ display: flex;
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+}
+
+.grid2,
+.grid1 {
+ display: flex;
+ flex-direction: column;
+}
+
+.grid2Label {
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-bottom: 20px;
+}
+
+.grid21 {
+ display: flex;
+ flex-wrap: wrap;
+
+ :global {
+ .radioButtonContainer {
+ margin-right: 60px;
+ height: 60px;
+ justify-content: space-around;
+ }
+ }
+}
+
+.grid1 {
+ margin-right: 100px;
+
+ :global {
+ .radioButtonContainer {
+ display: flex;
+ flex-direction: row;
+ margin-top: 20px;
+ width: 76px;
+ justify-content: space-between;
+
+ &:nth-child(2) {
+ width: 73px;
+ }
+
+ &:nth-child(3) {
+ width: 69px;
+ }
+ }
+ }
+}
+
+.grid2 {
+ margin-top: 20px;
+}
diff --git a/src/shared/components/examples/GUIKit/TextInput/index.jsx b/src/shared/components/examples/GUIKit/TextInput/index.jsx
new file mode 100644
index 0000000000..527108f6f2
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/TextInput/index.jsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import TextInput from 'components/GUIKit/TextInput';
+import PT from 'prop-types';
+
+import './style.scss';
+
+function TextInputExample({ size }) {
+ const values = [
+ {
+ key: 2,
+ placeholder: 'Pick a username',
+ label: '',
+ value: '',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 3,
+ placeholder: 'Pick a username',
+ label: 'Username',
+ value: 'Adam Morehead',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ placeholder: 'Pick a username',
+ label: 'Username',
+ value: 'Adam Morehead',
+ required: false,
+ errorMsg: 'This username is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 5,
+ placeholder: 'Pick a username',
+ label: 'Username',
+ value: 'Adam Morehead',
+ required: true,
+ errorMsg: '',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+
+
+ ))}
+
+ );
+}
+
+TextInputExample.defaultProps = {
+ size: 'lg',
+};
+
+TextInputExample.propTypes = {
+ size: PT.oneOf(['xs', 'lg']),
+};
+
+export default TextInputExample;
diff --git a/src/shared/components/examples/GUIKit/TextInput/style.scss b/src/shared/components/examples/GUIKit/TextInput/style.scss
new file mode 100644
index 0000000000..6ee600c2c7
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/TextInput/style.scss
@@ -0,0 +1,31 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .textInputContainer {
+ flex-grow: 1;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ flex-shrink: 0;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/Textarea/index.jsx b/src/shared/components/examples/GUIKit/Textarea/index.jsx
new file mode 100644
index 0000000000..972b0f87c9
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Textarea/index.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import Textarea from 'components/GUIKit/Textarea';
+
+import './style.scss';
+
+export default function TextareaExample() {
+ const values = [
+ {
+ key: 2,
+ placeholder: 'Briefly describe your request',
+ label: '',
+ value: '',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 3,
+ placeholder: 'Briefly describe your request',
+ label: 'Briefly describe your request',
+ value: 'Different members have differet reasons for joining Topcoder and that’s why I would like to propose one thing.',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ placeholder: 'Briefly describe your request',
+ label: 'Briefly describe your request',
+ value: 'Different members have differet reasons for joining Topcoder and that’s why I would like to propose one thing.',
+ required: false,
+ errorMsg: 'Your request is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 5,
+ placeholder: 'Briefly describe your request',
+ label: 'Briefly describe your request',
+ value: 'Different members have differet reasons for joining Topcoder and that’s why I would like to propose one thing.',
+ required: true,
+ errorMsg: '',
+ sectionTitle: 'Require',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+
+
+ ))}
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Textarea/style.scss b/src/shared/components/examples/GUIKit/Textarea/style.scss
new file mode 100644
index 0000000000..f82e84ed50
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Textarea/style.scss
@@ -0,0 +1,33 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .textareaContainer {
+ flex-grow: 1;
+ height: 176px;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ min-height: 176px;
+ flex-shrink: 0;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ margin-top: 32px;
+ flex-shrink: 0;
+}
diff --git a/src/shared/components/examples/GUIKit/Toggles/index.jsx b/src/shared/components/examples/GUIKit/Toggles/index.jsx
new file mode 100644
index 0000000000..e043b124ef
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Toggles/index.jsx
@@ -0,0 +1,127 @@
+import React from 'react';
+import Toggles from 'components/GUIKit/Toggles';
+
+import './style.scss';
+
+export default function TogglesExample() {
+ const checkValues = [
+ {
+ label: '',
+ key: 1,
+ checked: false,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 2,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 3,
+ checked: false,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 4,
+ checked: true,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 5,
+ checked: false,
+ size: 'xs',
+ },
+ {
+ label: '',
+ key: 6,
+ checked: true,
+ size: 'xs',
+ },
+ {
+ label: 'Option 1',
+ key: 7,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: 'Option 2',
+ key: 10,
+ checked: false,
+ size: 'lg',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+
+
+
+ onChange(checked)}
+ size={checkValues[0].size}
+ checked={checkValues[0].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[1].size}
+ checked={checkValues[1].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[2].size}
+ checked={checkValues[2].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[3].size}
+ checked={checkValues[3].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[4].size}
+ checked={checkValues[4].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[5].size}
+ checked={checkValues[5].checked}
+ />
+
+
+
+
Examples usage
+
+ {checkValues[6].label}
+ onChange(checked)}
+ size={checkValues[6].size}
+ checked={checkValues[6].checked}
+ />
+
+
+ {checkValues[7].label}
+ onChange(checked)}
+ size={checkValues[7].size}
+ checked={checkValues[7].checked}
+ />
+
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Toggles/style.scss b/src/shared/components/examples/GUIKit/Toggles/style.scss
new file mode 100644
index 0000000000..00b21bdab6
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Toggles/style.scss
@@ -0,0 +1,45 @@
+@import '~styles/mixins';
+
+.container {
+ display: flex;
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+}
+
+.grid1,
+.grid2 {
+ display: flex;
+ flex-direction: column;
+}
+
+.grid2 {
+ margin-left: 50px;
+ margin-top: 20px;
+}
+
+.grid11 {
+ display: flex;
+ margin-top: 20px;
+}
+
+.grid111 {
+ width: 70px;
+}
+
+.grid2Label {
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-bottom: 2px;
+}
+
+.grid21 {
+ display: flex;
+ align-items: center;
+ margin-top: 18px;
+}
+
+.grid21Label {
+ font-size: 14px;
+ margin-right: 10px;
+}
diff --git a/src/shared/components/examples/GUIKit/index.jsx b/src/shared/components/examples/GUIKit/index.jsx
new file mode 100644
index 0000000000..5836630ed6
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/index.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import CheckboxExample from './Checkbox';
+import TextInputExample from './TextInput';
+import TextareaExample from './Textarea';
+import RadioButtonExample from './RadioButton';
+import DatepickerExample from './Datepicker';
+import DropdownTermsExample from './DropdownTerms';
+import TogglesExample from './Toggles';
+import DropdownExample from './Dropdown';
+
+import './style.scss';
+
+export default function GUIKit() {
+ return (
+
+
TEXTBOX
+
+
+ TEXTBOX SMALL
+
+
+ TEXTAREA
+
+
+ CHECKBOXES
+
+
+ RADIO BUTTONS
+
+
+ DATE PICKER
+
+
+ DROPDOWN SKILLS
+
+
+ TOGGLES
+
+
+ DROPDOWN
+
+
+ DROPDOWN SMALL
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/style.scss b/src/shared/components/examples/GUIKit/style.scss
new file mode 100644
index 0000000000..2d9af1a8b5
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/style.scss
@@ -0,0 +1,16 @@
+.container {
+ padding: 20px;
+ padding-bottom: 100px;
+
+ h1 {
+ font-size: 34px;
+ font-weight: 500 !important;
+ }
+}
+
+.sectionTitle {
+ text-transform: uppercase;
+ font-size: 20px;
+ font-weight: 600 !important;
+ margin-top: 50px;
+}
diff --git a/src/shared/containers/Gigs/RecruitCRMJobs.jsx b/src/shared/containers/Gigs/RecruitCRMJobs.jsx
index d09ba71afc..46b5b50d6e 100644
--- a/src/shared/containers/Gigs/RecruitCRMJobs.jsx
+++ b/src/shared/containers/Gigs/RecruitCRMJobs.jsx
@@ -8,12 +8,22 @@ import LoadingIndicator from 'components/LoadingIndicator';
import SearchCombo from 'components/GUIKit/SearchCombo';
import Paginate from 'components/GUIKit/Paginate';
import JobListCard from 'components/GUIKit/JobListCard';
+import Dropdown from 'components/GUIKit/Dropdown';
import PT from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import './jobLisingStyles.scss';
const GIGS_PER_PAGE = 10;
+// Sort by dropdown
+const sortByOptions = [
+ { label: 'Latest Added Descending', selected: true },
+ { label: 'Latest Updated Descending', selected: false },
+];
+// Locations
+const locations = [{
+ label: 'Anywhere', selected: true,
+}];
class RecruitCRMJobsContainer extends React.Component {
constructor(props) {
@@ -23,11 +33,14 @@ class RecruitCRMJobsContainer extends React.Component {
term: '',
page: 0,
sortBy: 'created_on',
+ location: 'Anywhere',
};
-
+ // binds
this.onSearch = this.onSearch.bind(this);
this.onPaginate = this.onPaginate.bind(this);
this.onFilter = this.onFilter.bind(this);
+ this.onLocation = this.onLocation.bind(this);
+ this.onSort = this.onSort.bind(this);
}
componentDidMount() {
@@ -69,6 +82,22 @@ class RecruitCRMJobsContainer extends React.Component {
});
}
+ onLocation(newLocation) {
+ const selected = _.find(newLocation, { selected: true });
+ this.onFilter({
+ location: selected.label,
+ page: 0,
+ });
+ }
+
+ onSort(newSort) {
+ const selected = _.find(newSort, { selected: true });
+ this.onFilter({
+ sortBy: selected.label === 'Latest Updated Descending' ? 'updated_on' : 'created_on',
+ page: 0,
+ });
+ }
+
render() {
const {
loading,
@@ -78,6 +107,7 @@ class RecruitCRMJobsContainer extends React.Component {
term,
page,
sortBy,
+ location,
} = this.state;
if (loading) {
@@ -85,9 +115,21 @@ class RecruitCRMJobsContainer extends React.Component {
}
let jobsToDisplay = jobs;
+ // build current locations dropdown based on all data
+ // and filter by selected location
+ jobsToDisplay = _.filter(jobs, (job) => {
+ // build dropdown
+ const found = _.find(locations, { label: job.country });
+ if (!found) {
+ locations.push({ label: job.country, selected: location === job.country });
+ }
+ // filter
+ if (location === 'Anywhere' || location === 'Any' || location === 'Any Location') return true;
+ return location.toLowerCase() === job.country.toLowerCase();
+ });
// Filter by term
if (term) {
- jobsToDisplay = _.filter(jobs, (job) => {
+ jobsToDisplay = _.filter(jobsToDisplay, (job) => {
// eslint-disable-next-line no-underscore-dangle
const _term = term.toLowerCase();
// name search
@@ -115,6 +157,8 @@ class RecruitCRMJobsContainer extends React.Component {
+
+
{
diff --git a/src/shared/containers/Gigs/jobLisingStyles.scss b/src/shared/containers/Gigs/jobLisingStyles.scss
index 4edc9b7c5b..1d909ee9c1 100644
--- a/src/shared/containers/Gigs/jobLisingStyles.scss
+++ b/src/shared/containers/Gigs/jobLisingStyles.scss
@@ -10,6 +10,28 @@
.filters {
display: flex;
+ align-items: flex-end;
+
+ @include xs-to-sm {
+ flex-direction: column;
+ }
+
+ > div {
+ margin-right: 30px;
+ flex: 1;
+
+ @include xs-to-sm {
+ margin-right: 0;
+ }
+
+ &:first-child {
+ flex: 3;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
}
.jobs-list-container {
diff --git a/src/shared/routes/Examples/Examples.jsx b/src/shared/routes/Examples/Examples.jsx
index 04fb03a6fe..8c9bcd1403 100644
--- a/src/shared/routes/Examples/Examples.jsx
+++ b/src/shared/routes/Examples/Examples.jsx
@@ -30,6 +30,7 @@ import SearchBarExample from 'components/examples/SearchBar';
import TracksTreeExample from 'components/examples/TracksTree';
import TracksFilterExample from 'components/examples/TracksFilter';
import SearchPageFilterExample from 'components/examples/SearchPageFilter';
+import GUIKit from 'components/examples/GUIKit';
import {
Switch,
@@ -66,6 +67,7 @@ export default function Examples({
/>
+
diff --git a/src/shared/utils/useWindowSize.js b/src/shared/utils/useWindowSize.js
new file mode 100644
index 0000000000..3c71376c9c
--- /dev/null
+++ b/src/shared/utils/useWindowSize.js
@@ -0,0 +1,29 @@
+import React from 'react';
+
+/**
+ * Get window size
+ */
+export default function useWindowSize() {
+ const isSSR = typeof window !== 'undefined';
+ const [windowSize, setWindowSize] = React.useState({
+ width: isSSR ? window.innerWidth : 1200,
+ height: isSSR ? window.innerHeight : 800,
+ });
+
+ function changeWindowSize() {
+ setWindowSize({
+ width: isSSR ? window.innerWidth : 1200,
+ height: isSSR ? window.innerHeight : 800,
+ });
+ }
+
+ React.useEffect(() => {
+ window.addEventListener('resize', changeWindowSize);
+
+ return () => {
+ window.removeEventListener('resize', changeWindowSize);
+ };
+ }, []);
+
+ return windowSize;
+}