Compare commits

...

7 Commits

Author SHA1 Message Date
Alexander Kiselev
197c85d13c Copied delay utility function into Cypress backend
runner to decouple from incoming TS refactors
2021-10-07 13:52:36 -07:00
Alexander Kiselev
b4e2f5469f added type roots 2021-10-07 11:56:52 -07:00
Alexander Kiselev
d29be80ad7 now update Icon 2021-10-07 10:35:26 -07:00
Alexander Kiselev
24f380f0aa rename icon again 2021-10-07 10:35:20 -07:00
Alexander Kiselev
443db9cb0e fixed more updates 2021-10-07 10:34:56 -07:00
Alexander Kiselev
183f02474b Fix collapse section 🤦 2021-10-07 10:34:34 -07:00
Alexander Kiselev
4fc3652fea ported some really basic styled components files to typescript 2021-10-07 10:33:48 -07:00
22 changed files with 332 additions and 208 deletions

View File

@@ -4,7 +4,7 @@ import PropTypes from "prop-types";
import Icon from "metabase/components/Icon";
import Button from "metabase/components/Button";
import { cancelable } from "metabase/lib/promise";
import { cancelable, CancelablePromise } from "metabase/lib/promise";
import { t } from "ttag";
import cx from "classnames";
@@ -26,12 +26,10 @@ type State = {
result: null | "success" | "failed",
};
export default class ActionButton extends Component {
props: Props;
state: State;
timeout: ?any;
actionPromise: ?{ cancel: () => void };
export default class ActionButton extends Component<Props, State> {
timeout?: any;
actionPromise?: CancelablePromise<any>;
constructor(props: Props) {
super(props);
@@ -77,7 +75,7 @@ export default class ActionButton extends Component {
);
};
onClick = (event: MouseEvent) => {
onClick = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
// set state to active
@@ -139,8 +137,8 @@ export default class ActionButton extends Component {
? cx("Button", "Button--waiting")
: cx(className, {
"Button--waiting": active,
[successClassName]: result === "success",
[failedClassName]: result === "failed",
[successClassName || ""]: result === "success",
[failedClassName || ""]: result === "failed",
"pointer-events-none": isActionDisabled,
})
}

View File

@@ -1,7 +1,7 @@
import styled from "styled-components";
import { Flex } from "grid-styled";
export const CardContent = styled(Flex)`
export const CardContent = styled(Flex)<{ shouldDisplayHelpLink?: boolean }>`
margin-top: ${props => (props.shouldDisplayHelpLink ? "8px" : 0)};
`;
@@ -11,7 +11,7 @@ export const HelpCardContainer = styled(Flex)`
min-width: 300px;
`;
export const IconContainer = styled(Flex)`
export const IconContainer = styled(Flex)<{ hasCircle?: boolean }>`
width: ${props => (props.hasCircle ? "52px" : "32px")};
height: ${props => (props.hasCircle ? "52px" : "32px")};
background-color: ${props => (props.hasCircle ? "#EEF2F5" : "transparent")};

View File

@@ -8,7 +8,26 @@ import _ from "underscore";
import styled from "styled-components";
import { color, space } from "styled-system";
const BUTTON_VARIANTS = [
type Variants =
| "small"
| "medium"
| "large"
| "round"
| "primary"
| "danger"
| "warning"
| "cancel"
| "success"
| "purple"
| "white"
| "borderless"
| "onlyIcon";
type Props = PropTypes.InferProps<typeof propTypes & {
[name in Variants]: boolean
}>;
const BUTTON_VARIANTS: Variants[] = [
"small",
"medium",
"large",
@@ -24,7 +43,7 @@ const BUTTON_VARIANTS = [
"onlyIcon",
];
const BaseButton = ({
const BaseButton: React.FC<Props> = ({
className,
icon,
iconRight,
@@ -77,7 +96,7 @@ const BaseButton = ({
);
};
BaseButton.propTypes = {
const propTypes = {
className: PropTypes.string,
icon: PropTypes.string,
iconSize: PropTypes.number,
@@ -95,6 +114,8 @@ BaseButton.propTypes = {
borderless: PropTypes.bool,
};
BaseButton.propTypes = propTypes;
const Button = styled(BaseButton)`
${color};
${space};

View File

@@ -9,8 +9,10 @@ import moment from "moment";
import { t } from "ttag";
import Icon from "metabase/components/Icon";
export default class Calendar extends Component {
constructor(props) {
type Props = PropTypes.InferProps<typeof Calendar["propTypes"]>;
export default class Calendar extends Component<Props> {
constructor(props: Props) {
super(props);
this.state = {
current: moment(props.initial || undefined),
@@ -28,7 +30,7 @@ export default class Calendar extends Component {
isRangePicker: true,
};
UNSAFE_componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (
// `selected` became null or not null
(nextProps.selected == null) !== (this.props.selected == null) ||

View File

@@ -1,6 +1,6 @@
import styled from "styled-components";
export const ClampedDiv = styled.div`
export const ClampedDiv = styled.div<{visibleLines: number | null}>`
max-height: ${props =>
props.visibleLines == null
? "unset"

View File

@@ -3,7 +3,7 @@ import Icon from "metabase/components/Icon";
export const HeaderContainer = styled.div.attrs({
role: "button",
tabIndex: "0",
tabIndex: 0,
})`
display: flex;
align-items: center;
@@ -15,7 +15,11 @@ export const Header = styled.span`
align-items: center;
`;
const ICON_VARIANTS = {
type Variants = "right-down" | "up-down";
const ICON_VARIANTS: {
[name in Variants]: { collapsed: string; expanded: string };
} = {
"right-down": {
collapsed: "chevronright",
expanded: "chevrondown",
@@ -26,13 +30,19 @@ const ICON_VARIANTS = {
},
};
type Props = {
variant: Variants;
isExpanded: boolean;
position?: "left" | "right";
};
export const ToggleIcon = styled(Icon).attrs({
name: ({ isExpanded, variant }) => {
name: ({ isExpanded, variant }: Props) => {
const { collapsed, expanded } = ICON_VARIANTS[variant];
return isExpanded ? expanded : collapsed;
},
size: 12,
})`
})<Props>`
${props => css`
margin-${props.position === "left" ? "right" : "left"}: 0.5rem;
`};

View File

@@ -14,7 +14,9 @@ const propTypes = {
iconPosition: PropTypes.oneOf(["left", "right"]),
};
function CollapseSection({
type Props = PropTypes.InferProps<typeof propTypes>;
const CollapseSection: React.FC<Props> = ({
initialState = "collapsed",
iconVariant = "right-down",
iconPosition = "left",
@@ -23,7 +25,7 @@ function CollapseSection({
className,
bodyClass,
children,
}) {
}) => {
const [isExpanded, setIsExpanded] = useState(initialState === "expanded");
const toggle = useCallback(() => {
@@ -67,4 +69,4 @@ function CollapseSection({
CollapseSection.propTypes = propTypes;
export default CollapseSection;
export default CollapseSection;

View File

@@ -11,7 +11,7 @@ export const CardContent = styled.div`
justify-content: center;
`;
export const IconContainer = styled.div`
export const IconContainer = styled.div<{ hasCircle?: boolean }>`
border-radius: 99px !important;
flex-shrink: 0;
display: flex;
@@ -37,7 +37,7 @@ export const WarningIcon = styled(Icon)`
color: ${color("accent5")};
`;
export const WarningParagraph = styled.p`
export const WarningParagraph = styled.p<{ hasMargin?: boolean }>`
margin: ${props => (props.hasMargin ? `${space(1)} 0;` : 0)};
color: ${color("text-medium")};
`;

View File

@@ -2,7 +2,9 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
class EditBar extends Component {
type Props = PropTypes.InferProps<typeof EditBar["propTypes"]>;
class EditBar extends Component<Props> {
static propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,

View File

@@ -3,7 +3,15 @@ import React from "react";
import { t } from "ttag";
import cx from "classnames";
export default class ErrorDetails extends React.Component {
type Props = {
// Should this be optional since we stringify it? Maybe `| any`?
details: string | Error;
centered?: boolean,
className?: string,
}
export default class ErrorDetails extends React.Component<Props> {
state = {
showError: false,
};

View File

@@ -7,7 +7,9 @@ import cx from "classnames";
// NOTE: currently relies on .QueryError CSS selectors residing in query_builder.css
const ErrorMessage = ({ title, type, message, action, className }) => {
type Props = PropTypes.InferProps<typeof propTypes>;
const ErrorMessage: React.FC<Props> = ({ title, type, message, action, className }) => {
return (
<div className={cx(className, "QueryError flex align-center")}>
<div className={`QueryError-image QueryError-image--${type}`} />
@@ -20,7 +22,7 @@ const ErrorMessage = ({ title, type, message, action, className }) => {
);
};
ErrorMessage.propTypes = {
const propTypes = {
title: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
@@ -28,4 +30,6 @@ ErrorMessage.propTypes = {
className: PropTypes.string,
};
ErrorMessage.propTypes = propTypes;
export default ErrorMessage;

View File

@@ -3,7 +3,14 @@ import React from "react";
import { getUrlTarget } from "metabase/lib/dom";
const ExternalLink = ({
type Props = {
href: string,
target?: string,
className?: string,
children?: React.ReactNode
}
const ExternalLink: React.FC<Props> = ({
href,
target = getUrlTarget(href),
className,

View File

@@ -1,131 +0,0 @@
/*eslint-disable react/no-danger */
import React, { Component } from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { color, space, hover } from "styled-system";
import cx from "classnames";
import { color as c } from "metabase/lib/colors";
import { loadIcon } from "metabase/icon_paths";
import { stripLayoutProps } from "metabase/lib/utils";
import Tooltipify from "metabase/hoc/Tooltipify";
export const IconWrapper = styled("div")`
${space};
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 99px;
cursor: pointer;
color: ${props => (props.open ? c("brand") : "inherit")};
// special cases for certain icons
// Icon-share has a taller viewvbox than most so to optically center
// the icon we need to translate it upwards
"> .icon.icon-share": {
transform: translateY(-2px);
}
${hover};
transition: all 300ms ease-in-out;
`;
IconWrapper.defaultProps = {
hover: {
backgroundColor: c("bg-medium"),
color: c("brand"),
},
};
const stringOrNumberPropType = PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]);
export const iconPropTypes = {
name: PropTypes.string.isRequired,
size: stringOrNumberPropType,
width: stringOrNumberPropType,
height: stringOrNumberPropType,
scale: stringOrNumberPropType,
tooltip: PropTypes.string,
defaultName: PropTypes.string,
className: PropTypes.string,
};
const defaultProps = {
defaultName: "unknown",
};
class BaseIcon extends Component {
render() {
const { name, defaultName, className, ...rest } = this.props;
const icon = loadIcon(name) || loadIcon(defaultName);
if (!icon) {
console.warn(`Icon "${name}" does not exist.`);
return <span />;
}
const props = {
...icon.attrs,
...stripLayoutProps(rest),
className: cx(icon.attrs.className, className),
};
for (const prop of ["width", "height", "size", "scale"]) {
if (typeof props[prop] === "string") {
props[prop] = parseInt(props[prop], 10);
}
}
if (props.size != null) {
props.width = props.size;
props.height = props.size;
}
if (props.scale != null && props.width != null && props.height != null) {
props.width *= props.scale;
props.height *= props.scale;
}
delete props.size, props.scale;
if (icon.img) {
// avoid passing `role="img"` to an actual image file
// eslint-disable-next-line no-unused-vars
const { role, ...rest } = props;
return (
<img
src={icon.img}
srcSet={`
${icon.img} 1x,
${icon.img_2x} 2x
`}
{...rest}
/>
);
} else if (icon.svg) {
return <svg {...props} dangerouslySetInnerHTML={{ __html: icon.svg }} />;
} else if (icon.path) {
return (
<svg {...props}>
<path d={icon.path} />
</svg>
);
} else {
console.warn(`Icon "${name}" must have an img, svg, or path`);
return <span />;
}
}
}
BaseIcon.propTypes = iconPropTypes;
BaseIcon.defaultProps = defaultProps;
const Icon = styled(BaseIcon)`
${space}
${color}
${hover}
flex-shrink: 0
`;
export default Tooltipify(Icon);

View File

@@ -0,0 +1,130 @@
/*eslint-disable react/no-danger */
import React, { Component } from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { color, space, hover } from "styled-system";
import cx from "classnames";
import { color as c } from "metabase/lib/colors";
import { loadIcon } from "metabase/icon_paths";
import { stripLayoutProps } from "metabase/lib/utils";
import Tooltipify from "metabase/hoc/Tooltipify";
type IconWrapperProps = {
space: any;
hover: any;
open: boolean;
}
export const IconWrapper = styled<IconWrapperProps, "div">("div")`
${space};
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 99px;
cursor: pointer;
color: ${props => (props.open ? c("brand") : "inherit")};
// special cases for certain icons
// Icon-share has a taller viewvbox than most so to optically center
// the icon we need to translate it upwards
"> .icon.icon-share": {
transform: translateY(-2px);
}
${hover};
transition: all 300ms ease-in-out;
`;
IconWrapper.defaultProps = {
hover: {
backgroundColor: c("bg-medium"),
color: c("brand"),
},
};
const stringOrNumberPropType = PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]);
export const iconPropTypes = {
name: PropTypes.string.isRequired,
size: stringOrNumberPropType,
width: stringOrNumberPropType,
height: stringOrNumberPropType,
scale: stringOrNumberPropType,
tooltip: PropTypes.string,
defaultName: PropTypes.string,
className: PropTypes.string,
};
type Props = PropTypes.InferProps<typeof iconPropTypes>;
const BaseIcon: React.FC<Props> = ({ name, defaultName, className, ...rest }) => {
const icon = loadIcon(name) || loadIcon(defaultName ?? "unknown");
if (!icon) {
console.warn(`Icon "${name}" does not exist.`);
return <span />;
}
const props = {
...icon.attrs,
...stripLayoutProps(rest),
className: cx(icon.attrs.className, className),
};
for (const prop of ["width", "height", "size", "scale"]) {
if (typeof props[prop] === "string") {
props[prop] = parseInt(props[prop], 10);
}
}
if (props.size != null) {
props.width = props.size;
props.height = props.size;
}
if (props.scale != null && props.width != null && props.height != null) {
props.width *= props.scale;
props.height *= props.scale;
}
delete props.size, props.scale;
if (icon.img) {
// avoid passing `role="img"` to an actual image file
// eslint-disable-next-line no-unused-vars
const { role, ...rest } = props;
return (
<img
src={icon.img}
srcSet={`
${icon.img} 1x,
${icon.img_2x} 2x
`}
{...rest}
/>
);
} else if (icon.svg) {
return <svg {...props} dangerouslySetInnerHTML={{ __html: icon.svg }} />;
} else if (icon.path) {
return (
<svg {...props}>
<path d={icon.path} />
</svg>
);
} else {
console.warn(`Icon "${name}" must have an img, svg, or path`);
return <span />;
}
}
BaseIcon.propTypes = iconPropTypes;
const Icon = styled(BaseIcon)`
${space}
${color}
${hover}
flex-shrink: 0
`;
export default Tooltipify(Icon);

View File

@@ -1,42 +0,0 @@
// return a promise wrapping the provided one but with a "cancel" method
export function cancelable(promise) {
let canceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
value => (canceled ? reject({ isCanceled: true }) : resolve(value)),
error => (canceled ? reject({ isCanceled: true }) : reject(error)),
);
});
wrappedPromise.cancel = function() {
canceled = true;
};
return wrappedPromise;
}
// if a promise doesn't resolve/reject within a given duration it will reject
export function timeout(promise, duration, error) {
return new Promise((resolve, reject) => {
promise.then(resolve, reject);
setTimeout(
() => reject(error || new Error("Operation timed out")),
duration,
);
});
}
// returns a promise that resolves after a given duration
export function delay(duration) {
return new Promise((resolve, reject) => setTimeout(resolve, duration));
}
export function defer() {
const deferrred = {};
deferrred.promise = new Promise((resolve, reject) => {
deferrred.resolve = resolve;
deferrred.reject = reject;
});
return deferrred;
}

View File

@@ -0,0 +1,56 @@
export type CancelablePromise<T> = PromiseLike<T> & { cancel: () => void };
// return a promise wrapping the provided one but with a "cancel" method
export function cancelable<T>(promise: PromiseLike<T>): CancelablePromise<T> {
let canceled = false;
const wrappedPromise: Partial<CancelablePromise<T>> = new Promise(
(resolve, reject) => {
promise.then(
value => (canceled ? reject({ isCanceled: true }) : resolve(value)),
error => (canceled ? reject({ isCanceled: true }) : reject(error)),
);
},
);
wrappedPromise.cancel = function() {
canceled = true;
};
return wrappedPromise as CancelablePromise<T>;
}
// if a promise doesn't resolve/reject within a given duration it will reject
export function timeout<T>(
promise: PromiseLike<T>,
duration: number,
error: any,
): Promise<T> {
return new Promise((resolve, reject) => {
promise.then(resolve, reject);
setTimeout(
() => reject(error || new Error("Operation timed out")),
duration,
);
});
}
// returns a promise that resolves after a given duration
export function delay(duration: number) {
return new Promise((resolve, reject) => setTimeout(resolve, duration));
}
type DeferredPromise<T> = {
promise: PromiseLike<T>;
resolve: (value: T) => void;
reject: (reason: any) => void;
};
export function defer<T = any>() {
const deferred: Partial<DeferredPromise<T>> = {};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred as DeferredPromise<T>;
}

View File

@@ -4,7 +4,6 @@ import path from "path";
import { spawn } from "child_process";
import fetch from "isomorphic-fetch";
import { delay } from "../../src/metabase/lib/promise";
export const DEFAULT_DB_KEY = "/test_db_fixture.db";
@@ -176,3 +175,8 @@ function createSharedResource(
},
};
}
// Copied here from `frontend/src/metabase/lib/promise.js` to decouple Cypress from Typescript
function delay(duration) {
return new Promise((resolve, reject) => setTimeout(resolve, duration));
}

View File

@@ -115,7 +115,12 @@
"@testing-library/jest-dom": "^4.0.0",
"@testing-library/react": "^11.0.2",
"@testing-library/user-event": "^12.6.0",
"@types/classnames": "2.2.11",
"@types/grid-styled": "^4.2.1",
"@types/react-motion": "^0.0.31",
"@types/styled-components": "^3.0.4",
"@types/styled-system": "^2.2",
"@types/underscore": "^1.11.3",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"babel-eslint": "10.1.0",

View File

@@ -17,7 +17,8 @@
"./enterprise/frontend/test/*"
]
},
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"typeRoots": ["./node_modules/@types"]
},
"include": [
"frontend/src/**/*.ts",

View File

@@ -2270,6 +2270,11 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/classnames@2.2.11":
version "2.2.11"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.11.tgz#2521cc86f69d15c5b90664e4829d84566052c1cf"
integrity sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==
"@types/classnames@^2.2.9":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd"
@@ -2359,6 +2364,14 @@
dependencies:
"@types/node" "*"
"@types/grid-styled@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@types/grid-styled/-/grid-styled-4.2.1.tgz#619d7bbdbb14d161b9c2f42181f57b3a9d6ee779"
integrity sha512-cSVfTjuds7EXlhvShS8tJvN88hni63C9YfBAca1mADJ4c9AG7cN9646GJEI3eOEvqWRjfpAtfkKiqpv5JrIRpA==
dependencies:
"@types/react" "*"
styled-components "2.x - 3.x"
"@types/hast@^2.0.0":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.2.tgz#236201acca9e2695e42f713d7dd4f151dc2982e4"
@@ -2492,6 +2505,13 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
"@types/react-motion@^0.0.31":
version "0.0.31"
resolved "https://registry.yarnpkg.com/@types/react-motion/-/react-motion-0.0.31.tgz#93e2abfc5056c5c0f47650ff9ed23533857245c5"
integrity sha512-ncpSpnTE52QLZQTM8cuwhHA/ChpDmjfLzACihK7X6cz4DtqlIhA4RzMEsku/3WevjBMsFlbM+XJCwC2RalfDHw==
dependencies:
"@types/react" "*"
"@types/react@*":
version "17.0.14"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.14.tgz#f0629761ca02945c4e8fea99b8177f4c5c61fb0f"
@@ -2536,6 +2556,13 @@
"@types/node" "*"
"@types/react" "*"
"@types/styled-system@^2.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/styled-system/-/styled-system-2.3.2.tgz#d279ae1fab1f724f3fafe63a997f81aa590167ea"
integrity sha512-KrzVA59wZ9nPLzCKLjw6ylKZaYEmTX6ZvhnFK7pQv9QBs7iQ9QP1ITxLeNvhBlEDinfFnpSW7vBbxZrLZwU7pA==
dependencies:
styled-components "^3.3.2"
"@types/testing-library__cypress@^5.0.3":
version "5.0.8"
resolved "https://registry.yarnpkg.com/@types/testing-library__cypress/-/testing-library__cypress-5.0.8.tgz#6a8effce8218c5be42c1775b26dcbdcc273fda62"
@@ -2551,6 +2578,11 @@
dependencies:
pretty-format "^24.3.0"
"@types/underscore@^1.11.3":
version "1.11.3"
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.11.3.tgz#d6734f3741ce41b2630018c6b61c6745f6188c07"
integrity sha512-Fl1TX1dapfXyDqFg2ic9M+vlXRktcPJrc4PR7sRc7sdVrjavg/JHlbUXBt8qWWqhJrmSqg3RNAkAPRiOYw6Ahw==
"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.5.tgz#fdd299f23205c3455af88ce618dd65c14cb73e22"
@@ -14700,6 +14732,21 @@ style-to-object@^0.3.0:
dependencies:
inline-style-parser "0.1.1"
"styled-components@2.x - 3.x", styled-components@^3.3.2:
version "3.4.10"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.4.10.tgz#9a654c50ea2b516c36ade57ddcfa296bf85c96e1"
integrity sha512-TA8ip8LoILgmSAFd3r326pKtXytUUGu5YWuqZcOQVwVVwB6XqUMn4MHW2IuYJ/HAD81jLrdQed8YWfLSG1LX4Q==
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
fbjs "^0.8.16"
hoist-non-react-statics "^2.5.0"
prop-types "^15.5.4"
react-is "^16.3.1"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
supports-color "^3.2.3"
styled-components@3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.6.tgz#99e6e75a746bdedd295a17e03dd1493055a1cc3b"