diff --git a/packages/storybook-ui-components/stories/Input.stories.tsx b/packages/storybook-ui-components/stories/Input.stories.tsx new file mode 100644 index 000000000..51b313c2d --- /dev/null +++ b/packages/storybook-ui-components/stories/Input.stories.tsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; +import { withKnobs, text, boolean, number } from "@storybook/addon-knobs"; +import { NumberInput, TextInput } from "@cockroachlabs/ui-components"; +import { Search } from "@cockroachlabs/icons"; + +export default { + title: "Input", + components: [NumberInput, TextInput], + decorators: [withKnobs], +}; + +export const Number = () => { + const [value, setValue] = useState(); + return ( + + ); +}; + +export const Text = () => { + const [value, setValue] = useState(); + return ( + + ); +}; + +export const SearchInput = () => { + const [value, setValue] = useState(); + return ( + } + /> + ); +}; + +export const NumberInputWithIcon = () => { + const [value, setValue] = useState(); + return ( + } + /> + ); +}; diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 0893cc4f0..aa34def49 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -18,6 +18,9 @@ }, "keywords": [], "license": "MIT", + "dependencies": { + "@cockroachlabs/icons": "^0.1.1" + }, "devDependencies": { "@babel/cli": "^7.8.3", "@babel/core": "^7.8.3", diff --git a/packages/ui-components/src/Input/BaseInput.tsx b/packages/ui-components/src/Input/BaseInput.tsx new file mode 100644 index 000000000..48e2697f5 --- /dev/null +++ b/packages/ui-components/src/Input/BaseInput.tsx @@ -0,0 +1,102 @@ +import React, { + ChangeEvent, + CSSProperties, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; +import classNames from "classnames/bind"; +import styles from "./styles.module.scss"; + +type OwnInputProps = { + type?: T extends string ? "text" : "number"; + initialValue?: T; + value?: T; + onChange?: (value: T) => void; + className?: string; + style?: CSSProperties; + autoComplete?: string; + placeholder?: string; + name?: string; + disabled?: boolean; + invalid?: boolean; + prefixIcon?: React.ReactNode; +}; + +type NativeInputProps = Omit< + React.InputHTMLAttributes, + keyof OwnInputProps +>; + +export type InputProps = NativeInputProps & + OwnInputProps; + +const cx = classNames.bind(styles); + +export const BaseInput: React.FC = ({ + name, + type = "text", + autoComplete = "off", + className, + style, + value: outerValue = "", + initialValue = "", + placeholder, + onChange, + disabled = false, + invalid = false, + ...props +}) => { + const [value, setValue] = useState( + outerValue || initialValue, + ); + const [isDirty, setDirtyState] = useState(false); + + useEffect(() => { + if (!outerValue && !isDirty) { + return; + } + setDirtyState(true); + setValue(outerValue); + }, [outerValue, isDirty]); + + const onChangeHandler = useCallback( + (event: ChangeEvent) => { + const nextValue = event.target.value; + if (onChange && !disabled) { + onChange(nextValue); + } + }, + [onChange, disabled], + ); + + const classnames = useMemo( + () => + cx( + "input", + { + disabled, + invalid, + active: !disabled && !invalid, + }, + className, + ), + [className, invalid, disabled], + ); + + return ( + + ); +}; diff --git a/packages/ui-components/src/Input/NumberInput.tsx b/packages/ui-components/src/Input/NumberInput.tsx new file mode 100644 index 000000000..b6590c7f1 --- /dev/null +++ b/packages/ui-components/src/Input/NumberInput.tsx @@ -0,0 +1,78 @@ +import React, { useCallback, useState } from "react"; +import classNames from "classnames/bind"; +import { CaretUp, CaretDown } from "@cockroachlabs/icons"; +import isNumber from "../utils/isNumber"; +import { BaseInput, InputProps } from "./BaseInput"; +import styles from "./styles.module.scss"; +import { InputPrefix, InputWrapper } from "./helpers"; + +const cx = classNames.bind(styles); + +export type NumberInputProps = Omit, "type">; + +export const NumberInput: React.FC = ({ + onChange, + value: outerValue, + initialValue, + prefixIcon, + invalid, + disabled, + ...props +}) => { + const [value, setValue] = useState(outerValue || initialValue || 0); + const onSpinClickHandler = useCallback( + (increase: -1 | 1) => () => { + if (disabled) { + return; + } + const nextValue = value + increase; + setValue(nextValue); + onChange(nextValue); + }, + [value, onChange, disabled], + ); + + const onChangeHandler = useCallback( + (nextValue: string) => { + const parsedValue = Number(nextValue); + if (!isNumber(parsedValue)) { + return; + } + setValue(parsedValue); + onChange(parsedValue); + }, + [onChange], + ); + + const spinButtonsGroupClassName = cx("spin-buttons-group"); + const spinButton = cx("spin-button"); + + return ( + + {prefixIcon} + +
+ + +
+
+ ); +}; diff --git a/packages/ui-components/src/Input/README.md b/packages/ui-components/src/Input/README.md new file mode 100644 index 000000000..f0e2778ce --- /dev/null +++ b/packages/ui-components/src/Input/README.md @@ -0,0 +1,24 @@ +[back to components](../README.md) + +# Input components + +Provides `` and `` realizations and `` +unstyled base component to build own custom input components. + +## Properties +### `initialValue?: string | number` +Value to be displayed if no `value` is provided or nothing is entered by user. +### `value?: string | number` +Value to be displayed in input element +### `onChange?: (value: string | number) => void;` +Handler to be called when input value is changed by user +### `placeholder?: string;` +Placeholder to be shown when no value is present +### `disabled?: boolean;` +Disable input +### `invalid?: boolean;` +Highlight input element with red borders to indicate that entered value is not acceptable. +### `prefixIcon?: ReactNode` +Expects one of SVG icons (`@cockroachlabs/icons`) to be provided. + + diff --git a/packages/ui-components/src/Input/TextInput.tsx b/packages/ui-components/src/Input/TextInput.tsx new file mode 100644 index 000000000..e5d6ae313 --- /dev/null +++ b/packages/ui-components/src/Input/TextInput.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { BaseInput, InputProps } from "./BaseInput"; +import { InputPrefix, InputWrapper } from "./helpers"; + +export type TextInputProps = Omit, "type">; + +export const TextInput: React.FC = props => { + const { prefixIcon, disabled, invalid } = props; + return ( + + {prefixIcon} + + + ); +}; diff --git a/packages/ui-components/src/Input/constants.scss b/packages/ui-components/src/Input/constants.scss new file mode 100644 index 000000000..9d10146bc --- /dev/null +++ b/packages/ui-components/src/Input/constants.scss @@ -0,0 +1,17 @@ +@import "../styles/tokens.scss"; + +$input-height: 40px; +$input-border-spacing: 3px; +$border-width: 1px; +$input-number-width: 160px; +$input-text-width: 280px; + +// state, hoverColor, focusColor +$state-colors: + "active" $crl-neutral-5 $crl-base-blue, + "invalid" $crl-base-red, + "disabled" $crl-base-text--light; + +@function exclBorderWidth($total-width: 0, $spacing: $input-border-spacing, $border: $border-width) { + @return $total-width - ($border + $spacing) * 2; +} diff --git a/packages/ui-components/src/Input/helpers.tsx b/packages/ui-components/src/Input/helpers.tsx new file mode 100644 index 000000000..a8e56cc41 --- /dev/null +++ b/packages/ui-components/src/Input/helpers.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import classNames from "classnames/bind"; +import styles from "./styles.module.scss"; +import { InputProps } from "./BaseInput"; + +const cx = classNames.bind(styles); + +export type InputWrapperProps = Pick< + InputProps, + "disabled" | "invalid" | "className" +>; + +export interface InputPrefixProps { + className?: string; +} + +export const InputPrefix: React.FC = ({ + children, + className, +}) => { + if (!children) { + return null; + } + const classes = cx("prefix", className); + return
{children}
; +}; + +export const InputWrapper: React.FC = ({ + children, + className, + invalid, + disabled, +}) => { + const wrapperClassName = cx( + "container", + { + active: !invalid && !disabled, + disabled: disabled, + invalid: invalid, + }, + className, + ); + return
{children}
; +}; diff --git a/packages/ui-components/src/Input/index.ts b/packages/ui-components/src/Input/index.ts new file mode 100644 index 000000000..84511632b --- /dev/null +++ b/packages/ui-components/src/Input/index.ts @@ -0,0 +1,3 @@ +export * from "./BaseInput"; +export * from "./TextInput"; +export * from "./NumberInput"; diff --git a/packages/ui-components/src/Input/input.scss b/packages/ui-components/src/Input/input.scss new file mode 100644 index 000000000..c62c80afc --- /dev/null +++ b/packages/ui-components/src/Input/input.scss @@ -0,0 +1,92 @@ +@import "../styles/tokens.scss"; +@import "./constants.scss"; + +// ".container" class is responsible to style up outer borders +// and width, height sizes of input component. +// it can be applied to element itself or to wrapper
+// element in case NumberInput is rendered. +.container { + border-radius: $input-border-spacing; + height: exclBorderWidth($input-height); + display: inline-flex; + background-color: $crl-base-white; + min-width: exclBorderWidth($input-text-width); + width: exclBorderWidth($input-text-width); + padding: $input-border-spacing; + + &.active { + border: solid $border-width $crl-neutral-4; + background-color: $crl-base-white; + color: initial; + + &:hover { + border: solid $border-width $crl-neutral-5; + } + + &:focus-within { + border: solid $border-width $crl-base-blue; + // Outline params are borrowed from default outline styles for input element + // and applied to div element. + outline: $crl-base-blue auto 3px; + outline-offset: -2px; + } + } + + &.invalid { + border: solid $border-width $crl-base-red; + background-color: $crl-red-1; + color: $crl-base-red; + + &:focus-within, &:hover { + border: solid $border-width $crl-base-red; + } + } + + &.disabled { + background: $crl-neutral-1; + color: $crl-base-text--light; + border: solid $border-width $crl-neutral-3; + cursor: default; + + &:focus, &:hover { + border: solid $border-width $crl-neutral-3; + } + } +} + +// ".input" removes most styling from element because it is responsibility of `.container` class. +.input { + font-family: Source Sans Pro, sans-serif; // TODO (koorosh): replace with token value + font-size: 14px; // TODO (koorosh): replace with token value (default font size) + line-height: 22px; + border: none; + outline: none; + padding: 0 crl-gutters(1.5); + position: relative; + background-color: inherit; + color: inherit; + width: 100%; + + &:focus, &:hover { + border: none; + outline: none; + } +} + +// Prefix component styles. Set constant width/height +// and apply colors based on container's state. +.container { + .prefix { + width: 14px; // TODO (koorosh): has to be the same as font-size + min-width: 14px; + height: 14px; + min-height: 14px; + margin: auto 0 auto 8px; + } + + @each $state, $hoverColor, $focusColor in $state-colors { + &.#{$state} .prefix > svg { + fill: $hoverColor; + } + } +} diff --git a/packages/ui-components/src/Input/input.test.tsx b/packages/ui-components/src/Input/input.test.tsx new file mode 100644 index 000000000..d509ca1bb --- /dev/null +++ b/packages/ui-components/src/Input/input.test.tsx @@ -0,0 +1,169 @@ +import React from "react"; +import { shallow } from "enzyme"; +import { Search } from "@cockroachlabs/icons"; +import { BaseInput, InputProps, NumberInput, TextInput } from "./index"; + +describe("BaseInput", () => { + describe("Default props", () => { + it("provides correct default values to inner element", () => { + const wrapper = shallow(); + const props = wrapper.props(); + expect(props.disabled).toBeFalsy(); + expect(props.value).toEqual(""); + expect(props.className).toEqual("input active"); + expect(props.name).toBeUndefined(); + expect(props.style).toBeUndefined(); + expect(props.type).toEqual("text"); + }); + }); + + describe("Style classes", () => { + it("sets .disabled class when Input is disabled", () => { + const wrapper = shallow(); + expect(wrapper.hasClass("disabled")).toBeTruthy(); + }); + + it("sets .invalid class when Input is disabled", () => { + const wrapper = shallow(); + expect(wrapper.hasClass("invalid")).toBeTruthy(); + }); + }); + + describe("onChange handler", () => { + it("calls callback function with typed text", () => { + const onChangeSpyFn = jasmine.createSpy(); + const text = "some text"; + const wrapper = shallow(); + wrapper.simulate("change", { target: { value: text } }); + expect(onChangeSpyFn).toHaveBeenCalledWith(text); + }); + + it("does not call callback when Input is disabled", () => { + const onChangeSpyFn = jasmine.createSpy(); + const text = "some text"; + const wrapper = shallow( + , + ); + wrapper.simulate("change", { target: { value: text } }); + expect(onChangeSpyFn).not.toHaveBeenCalled(); + }); + }); +}); + +describe("TextInput", () => { + describe("Default props", () => { + it("provides correct default values to inner element", () => { + const wrapper = shallow(); + const inputWrapper = wrapper.find(BaseInput); + const props = inputWrapper.props(); + expect(props.disabled).toBeFalsy(); + expect(props.type).toEqual("text"); + expect(props.initialValue).toBeUndefined(); + expect(props.autoComplete).toBeUndefined(); + expect(props.value).toBeUndefined(); + expect(props.className).toBeUndefined(); + expect(props.name).toBeUndefined(); + expect(props.style).toBeUndefined(); + }); + }); + + it("applies native props and event handlers", () => { + const onSubmitSpyFn = jasmine.createSpy(); + const wrapper = shallow( + , + ); + const inputElWrapper = wrapper + .find(BaseInput) + .dive() + .find("input") + .at(0); + inputElWrapper.simulate("submit"); + expect(onSubmitSpyFn).toHaveBeenCalled(); + expect(inputElWrapper.prop("aria-label")).toEqual("number-input-el"); + }); +}); + +describe("NumberInput", () => { + describe("Input field", () => { + it("calls onChange function when type text", () => { + const onChangeSpyFn = jasmine.createSpy(); + const wrapper = shallow( + , + ); + const inputWrapper = wrapper.find(BaseInput); + inputWrapper.prop("onChange")("123"); + expect(onChangeSpyFn).toHaveBeenCalledWith(123); + }); + + it("ignores calls with non-digital characters", () => { + const onChangeSpyFn = jasmine.createSpy(); + const wrapper = shallow( + , + ); + const inputWrapper = wrapper.find(BaseInput); + inputWrapper.prop("onChange")("abc"); + expect(onChangeSpyFn).not.toHaveBeenCalled(); + }); + + it("applies native props and event handlers", () => { + const onSubmitSpyFn = jasmine.createSpy(); + const wrapper = shallow( + , + ); + const inputElWrapper = wrapper + .find(BaseInput) + .dive() + .find("input") + .at(0); + inputElWrapper.simulate("submit"); + expect(onSubmitSpyFn).toHaveBeenCalled(); + expect(inputElWrapper.prop("aria-label")).toEqual("number-input-el"); + }); + }); + + describe("Spin buttons handlers", () => { + it("increments value by clicking on 'up' spin button", () => { + const expectedValue = 2; + const onChangeSpyFn = jasmine.createSpy(); + const wrapper = shallow( + , + ); + wrapper.find(".spin-button-up").simulate("click"); + wrapper.find(".spin-button-up").simulate("click"); + wrapper.find(".spin-button-up").simulate("click"); + const inputWrapper = wrapper.find(BaseInput); + + expect(onChangeSpyFn).toHaveBeenLastCalledWith(expectedValue); + expect(inputWrapper.prop("value")).toEqual(expectedValue); + }); + + it("decrements value by clicking on 'down' spin button", () => { + const expectedValue = -2; + const onChangeSpyFn = jasmine.createSpy(); + const wrapper = shallow( + , + ); + wrapper.find(".spin-button-down").simulate("click"); + wrapper.find(".spin-button-down").simulate("click"); + wrapper.find(".spin-button-down").simulate("click"); + const inputWrapper = wrapper.find(BaseInput); + + expect(inputWrapper.prop("value")).toEqual(expectedValue); + expect(onChangeSpyFn).toHaveBeenLastCalledWith(expectedValue); + }); + }); +}); + +describe("Input with prefixed Icon", () => { + it("renders Icon with TextInput component", () => { + const wrapper = shallow(} />); + const prefixWrapper = wrapper.find(Search); + expect(prefixWrapper).toBeDefined(); + }); + + it("renders Icon with NumberInput component", () => { + const wrapper = shallow(} />); + const prefixWrapper = wrapper.find(Search); + expect(prefixWrapper).toBeDefined(); + }); +}); diff --git a/packages/ui-components/src/Input/number-input.scss b/packages/ui-components/src/Input/number-input.scss new file mode 100644 index 000000000..3e9d04d3d --- /dev/null +++ b/packages/ui-components/src/Input/number-input.scss @@ -0,0 +1,81 @@ +@import "../styles/tokens.scss"; +@import "./constants.scss"; + +// remove default arrow spinner for input (number type) +.input[type="number"]::-webkit-outer-spin-button, +.input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + pointer-events: none; +} + +// remove default arrow spinner for input -- Firefox browser +.input[type=number] { + -moz-appearance: textfield; + @include firefox-only { + width: -moz-available; + overflow-x: auto + } +} + +// Following block describes styles for "Up" and "Down" buttons +// in Number Input element. +.container { + .spin-buttons-group { + display: flex; + flex-direction: column; + justify-content: stretch; + align-items: stretch; + position: relative; + right: 0; + width: 32px; + min-width: 32px; + } + + .spin-button { + display: flex; + flex-grow: 1; + align-items: center; + align-self: center; + justify-content: center; + width: 100%; + border: none; + outline: none; + padding: 0; + background-color: inherit; + + > svg { + width: 8px; + opacity: 0.6; + + :hover > &, :focus-within > & { + opacity: 1; + } + } + } + + // Depending on .container's state (active, invalid, or disabled) + // we need to change colors for 'up' and 'down' buttons (Number Input) + // component. + // iterate over all element states (active, invalid, disabled) and + // depending on assigned state for parent (.container) class it + // customizes colors for "up" and "down" buttons for Number input. + @each $state, $hoverColor, $focusColor in $state-colors { + &.#{$state} .spin-button > svg { + fill: $hoverColor; + } + + @if $focusColor { + &.#{$state}:focus-within .spin-button > svg { + fill: $focusColor; + } + } + } + + &.number-type { + // Add extra space inside of container to make sure that inner element + // doesn't overlap rounded borders and outline with inner shadows + min-width: exclBorderWidth($input-number-width); + width: exclBorderWidth($input-number-width); + } +} diff --git a/packages/ui-components/src/Input/styles.module.scss b/packages/ui-components/src/Input/styles.module.scss new file mode 100644 index 000000000..dc46f6bc0 --- /dev/null +++ b/packages/ui-components/src/Input/styles.module.scss @@ -0,0 +1,2 @@ +@import "./input.scss"; +@import "./number-input.scss"; diff --git a/packages/ui-components/src/index.js b/packages/ui-components/src/index.js index 617553cb1..b92bb4e8c 100644 --- a/packages/ui-components/src/index.js +++ b/packages/ui-components/src/index.js @@ -1,2 +1,3 @@ export { Badge } from "./Badge"; export { Avatar } from "./Avatar"; +export { NumberInput, TextInput } from "./Input"; diff --git a/packages/ui-components/src/styles/tokens.scss b/packages/ui-components/src/styles/tokens.scss index 04c411f5e..ce89ff5fe 100644 --- a/packages/ui-components/src/styles/tokens.scss +++ b/packages/ui-components/src/styles/tokens.scss @@ -122,6 +122,16 @@ $crl-base-divider: $crl-neutral-5; } } +@mixin firefox-only { + @at-root { + @-moz-document url-prefix() { + & { + @content; + } + } + } +} + /** Variables */ diff --git a/packages/ui-components/src/utils/isNumber.ts b/packages/ui-components/src/utils/isNumber.ts new file mode 100644 index 000000000..7971bab68 --- /dev/null +++ b/packages/ui-components/src/utils/isNumber.ts @@ -0,0 +1,5 @@ +const isNumber = (value: unknown) => { + return typeof value === "number" && Number.isFinite(value); +}; + +export default isNumber; diff --git a/packages/ui-components/yarn.lock b/packages/ui-components/yarn.lock index d36b4fcce..ac2ae394f 100644 --- a/packages/ui-components/yarn.lock +++ b/packages/ui-components/yarn.lock @@ -931,6 +931,11 @@ "@typescript-eslint/parser" "^2.6.1" eslint-config-prettier "^6.5.0" +"@cockroachlabs/icons@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@cockroachlabs/icons/-/icons-0.1.1.tgz#691940350d32678aac6fcf3307054bd30f49e7a0" + integrity sha512-d7N8uJMGmrj11O1kPm/wLgZz7GBsqoQbOoeiLnkiQWfbg2oKfDPax5VvhDwCvvEN8YiL9VHAL0aGSjEFhB+55A== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" @@ -2809,13 +2814,6 @@ debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -2840,11 +2838,6 @@ deep-equal-ident@^1.1.1: dependencies: lodash.isequal "^3.0" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -2907,11 +2900,6 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3716,13 +3704,6 @@ fs-extra@^9.0.0: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -4131,7 +4112,7 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -4162,13 +4143,6 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -4270,7 +4244,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -5740,21 +5714,6 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -5861,15 +5820,6 @@ nearley@^2.7.10: randexp "0.4.6" semver "^5.4.1" -needle@^2.2.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" - integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - neo-async@^2.5.0, neo-async@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" @@ -5948,22 +5898,6 @@ node-notifier@^6.0.0: shellwords "^0.1.1" which "^1.3.1" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-releases@^1.1.53: version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" @@ -5999,14 +5933,6 @@ node-sass@^4.13.1: dependencies: abbrev "1" -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -6029,27 +5955,6 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -6064,7 +5969,7 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -6247,7 +6152,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@0, osenv@^0.1.4: +osenv@0: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -6858,16 +6763,6 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-dom@^16.12.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" @@ -7232,7 +7127,7 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: +rimraf@2, rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -7398,7 +7293,7 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -7921,11 +7816,6 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - style-loader@^1.1.3: version "1.1.4" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.1.4.tgz#1ad81283cefe51096756fd62697258edad933230" @@ -8004,19 +7894,6 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" -tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -8737,7 +8614,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==