feat(React Incubation): User Profile (#2083)

* feat(react incubation): user profile w/ mock data

* removing spurious build-and-test change

* fixing CI issues
This commit is contained in:
Gabe Lyons 2021-02-05 14:21:04 -08:00 committed by GitHub
parent b0801de7ad
commit dcefd96b4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 53965 additions and 21 deletions

View File

@ -26,8 +26,10 @@ module.exports = {
'no-debugger': 'warn',
'require-await': 'warn',
'import/prefer-default-export': 'off', // TODO: remove this lint rule
'import/extensions': 'off',
'react/jsx-props-no-spreading': 'off',
'no-plusplus': 'off',
'no-prototype-builtins': 'off',
'react/require-default-props': 'off',
'@typescript-eslint/no-unused-vars': [
'error',

53383
datahub-web-react/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@
"@types/react-router-dom": "^5.1.6",
"antd": "^4.9.4",
"graphql": "^15.4.0",
"history": "^5.0.0",
"js-cookie": "^2.2.1",
"query-string": "^6.13.8",
"react": "^17.0.0",
@ -25,6 +26,7 @@
"react-router": "^5.2.0",
"react-router-dom": "^5.1.6",
"react-scripts": "4.0.1",
"styled-components": "^5.2.1",
"typescript": "^4.1.3",
"web-vitals": "^0.2.4"
},
@ -70,6 +72,7 @@
"@storybook/preset-create-react-app": "^3.1.5",
"@types/graphql": "^14.5.0",
"@types/query-string": "^6.3.0",
"@types/styled-components": "^5.1.7",
"@typescript-eslint/eslint-plugin": "^4.11.0",
"@typescript-eslint/parser": "^4.11.0",
"babel-loader": "8.1.0",

View File

@ -38,6 +38,7 @@ export const Routes = (): JSX.Element => {
{entityRegistry.getEntities().map((entity) => (
<ProtectedRoute
key={entity.getPathName()}
isLoggedIn={isLoggedIn}
path={`/${entity.getPathName()}/:urn`}
render={() => <EntityPage entityType={entity.type} />}

View File

@ -0,0 +1,3 @@
export enum Subview {
Ownership = 'ownership',
}

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { CorpUser, EntityType } from '../../../types.generated';
import { Entity, PreviewType } from '../Entity';
import { UserPage } from './UserPage';
import UserProfile from './UserProfile';
/**
* Definition of the DataHub Dataset entity.
@ -19,7 +19,7 @@ export class UserEntity implements Entity<CorpUser> {
getCollectionName: () => string = () => 'Users';
renderProfile: (urn: string) => JSX.Element = (_) => <UserPage />;
renderProfile: (urn: string) => JSX.Element = (_) => <UserProfile />;
renderPreview = (_: PreviewType, _1: CorpUser) => <p>Hello</p>;
}

View File

@ -0,0 +1,82 @@
import { Menu } from 'antd';
import { MenuProps } from 'antd/lib/menu';
import React from 'react';
import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
import { EntityType } from '../../../types.generated';
import { useEntityRegistry } from '../../useEntityRegistry';
import { navigateToUserUrl } from './routingUtils/navigateToUserUrl';
import { Subview } from './Subview';
import UserOwnership from './UserOwnership';
const MENU_KEY_DELIMETER = '__';
const toMenuKey = (subview?: Subview, item?: string) => `${subview}${MENU_KEY_DELIMETER}${item}`;
const fromMenuKey = (key: string): { subview: string; item: string } => {
const parts = key.split(MENU_KEY_DELIMETER);
return { subview: parts[0], item: parts[1] };
};
const MenuWrapper = styled.div`
border: 2px solid #f5f5f5;
`;
const Content = styled.div`
margin-left: 32px;
flex-grow: 1;
`;
const DetailWrapper = styled.div`
display: inline-flex;
width: 100%;
`;
type Props = {
urn: string;
ownerships: { [key in EntityType]?: any[] };
subview?: Subview;
item?: string;
};
export default function UserDetails({ ownerships, subview, item, urn }: Props) {
const entityRegistry = useEntityRegistry();
const ownershipMenuOptions: Array<EntityType> = Object.keys(ownerships) as Array<EntityType>;
const history = useHistory();
const onMenuClick: MenuProps['onClick'] = ({ key }) => {
const { subview: nextSubview, item: nextItem } = fromMenuKey(String(key));
navigateToUserUrl({ urn, subview: nextSubview, item: nextItem, history, entityRegistry });
};
const subviews = Object.values(Subview);
const selectedKey = toMenuKey(subview, item);
return (
<DetailWrapper>
<MenuWrapper>
<Menu
selectable={false}
mode="inline"
style={{ width: 256 }}
openKeys={subviews}
selectedKeys={[selectedKey]}
onClick={onMenuClick}
>
<Menu.SubMenu key={Subview.Ownership} title="Ownership">
{ownershipMenuOptions.map((option) => (
<Menu.Item key={toMenuKey(Subview.Ownership, entityRegistry.getPathName(option))}>
{entityRegistry.getCollectionName(option)}
</Menu.Item>
))}
</Menu.SubMenu>
</Menu>
</MenuWrapper>
<Content>
{subview === Subview.Ownership && <UserOwnership ownerships={ownerships} entityPath={item} />}
</Content>
</DetailWrapper>
);
}

View File

@ -0,0 +1,69 @@
import { UserOutlined } from '@ant-design/icons';
import styled from 'styled-components';
import React from 'react';
import { Space, Badge, Typography, Avatar } from 'antd';
type Props = {
profileSrc?: string;
name: string;
title: string;
skills: string[];
teams: string[];
email: string;
};
const Row = styled.div`
display: inline-flex;
`;
const AvatarWrapper = styled.div`
margin-right: 32px;
`;
const Traits = styled.div`
display: inline-flex;
margin-top: 32px;
`;
const Skills = styled.div`
margin-right: 32px;
`;
export default function UserHeader({ profileSrc, name, title, skills, teams, email }: Props) {
return (
<Row>
<AvatarWrapper>
<Avatar icon={<UserOutlined />} src={profileSrc} size={100} />
</AvatarWrapper>
<div>
<Typography.Title level={3}>{name}</Typography.Title>
<Space split="|" size="middle">
<Typography.Text>{title}</Typography.Text>
<a href={`mailto:${email}`}>
<Typography.Text strong>{email}</Typography.Text>
</a>
</Space>
<div>
<Traits>
<Skills>
<Typography.Title level={5}>Ask me about</Typography.Title>
<Space>
{skills.map((skill) => (
<Badge style={{ backgroundColor: '#108ee9' }} count={skill} key={skill} />
))}
</Space>
</Skills>
<div>
<Typography.Title level={5}>Teams</Typography.Title>
<Space>
{teams.map((team) => (
<Badge style={{ backgroundColor: '#87d068' }} count={team} key={team} />
))}
</Space>
</div>
</Traits>
</div>
</div>
</Row>
);
}

View File

@ -0,0 +1,43 @@
import React from 'react';
import { List, Typography } from 'antd';
import styled from 'styled-components';
import { EntityType } from '../../../types.generated';
import { useEntityRegistry } from '../../useEntityRegistry';
import { PreviewType } from '../Entity';
type Props = {
ownerships: { [key in EntityType]?: any[] };
entityPath?: string;
};
const ListContainer = styled.div`
display: default;
flex-grow: default;
`;
export default ({ ownerships, entityPath }: Props) => {
const entityRegistry = useEntityRegistry();
const entityType = entityRegistry.getTypeFromPathName(entityPath || '');
if (!entityType) return null;
const entitiesToShow = ownerships[entityType] || [];
return (
<ListContainer>
<List
dataSource={entitiesToShow}
header={
<Typography.Title level={3}>
{entityRegistry.getCollectionName(entityType)} they own
</Typography.Title>
}
renderItem={(item) => {
return entityRegistry.renderPreview(entityType, PreviewType.PREVIEW, item);
}}
/>
</ListContainer>
);
};

View File

@ -1,8 +0,0 @@
import * as React from 'react';
/**
* Responsible for reading & writing users.
*/
export const UserPage: React.VFC = () => {
return <div>Needs Implemented</div>;
};

View File

@ -0,0 +1,54 @@
import { Divider } from 'antd';
import React from 'react';
import styled from 'styled-components';
import { EntityType, PlatformNativeType } from '../../../types.generated';
import UserHeader from './UserHeader';
import UserDetails from './UserDetails';
import useUserParams from './routingUtils/useUserParams';
const PageContainer = styled.div`
background-color: white;
padding: 32px 100px;
`;
/**
* Responsible for reading & writing users.
*/
export default function UserProfile() {
const { urn, subview, item } = useUserParams();
return (
<PageContainer>
<UserHeader
name="Jane Doe"
title="Software Engineer"
skills={['Pandas', 'Multivariate Calculus', 'Juggling']}
teams={['Product', 'Data Science']}
email="jane@datahub.ui"
/>
<Divider />
<UserDetails
urn={urn}
subview={subview}
item={item}
ownerships={{
[EntityType.Dataset]: [
{
name: 'HiveDataset',
origin: 'PROD',
description: 'this is a dataset',
platformNativeType: PlatformNativeType.Table,
},
{
name: 'KafkaDataset',
origin: 'PROD',
description: 'this is also a dataset',
platformNativeType: PlatformNativeType.Table,
},
],
}}
/>
</PageContainer>
);
}

View File

@ -0,0 +1,53 @@
import React from 'react';
import { render } from '@testing-library/react';
import UserDetails from '../UserDetails';
import { EntityType, PlatformNativeType } from '../../../../types.generated';
import TestPageContainer from '../../../../utils/test-utils/TestPageContainer';
import { Subview } from '../Subview';
const ownerships = {
[EntityType.Dataset]: [
{
name: 'HiveDataset',
origin: 'PROD',
description: 'this is a dataset',
platformNativeType: PlatformNativeType.Table,
},
{
name: 'KafkaDataset',
origin: 'PROD',
description: 'this is also a dataset',
platformNativeType: PlatformNativeType.Table,
},
],
};
describe('UserDetails', () => {
it('renders a menu with the ownership submenu and datasets option', () => {
const { getByText } = render(
<TestPageContainer>
<UserDetails urn="some:urn" ownerships={ownerships} />;
</TestPageContainer>,
);
expect(getByText('Ownership')).toBeInTheDocument();
expect(getByText('Datasets')).toBeInTheDocument();
});
it('will not the show the ownership details by default', () => {
const { queryByText } = render(
<TestPageContainer>
<UserDetails urn="some:urn" ownerships={ownerships} />;
</TestPageContainer>,
);
expect(queryByText('Datasets they own')).not.toBeInTheDocument();
});
it('will the show the ownership details when selected', () => {
const { getByText } = render(
<TestPageContainer>
<UserDetails urn="some:urn" ownerships={ownerships} subview={Subview.Ownership} item="dataset" />;
</TestPageContainer>,
);
expect(getByText('Datasets they own')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,35 @@
import React from 'react';
import { render } from '@testing-library/react';
import UserHeader from '../UserHeader';
describe('UserHeader', () => {
it('renders', () => {
render(
<UserHeader
name="Jane Doe"
title="Software Engineer"
skills={['Pandas', 'Multivariate Calculus', 'Juggling']}
teams={['Product', 'Data Science']}
email="jane@datahub.ui"
/>,
);
});
it('renders name, title, skills, teams and email', () => {
const { getByText } = render(
<UserHeader
name="Jane Doe"
title="Software Engineer"
skills={['Pandas', 'Multivariate Calculus', 'Juggling']}
teams={['Product', 'Data Science']}
email="jane@datahub.ui"
/>,
);
expect(getByText('Jane Doe')).toBeInTheDocument();
expect(getByText('Software Engineer')).toBeInTheDocument();
expect(getByText('Pandas')).toBeInTheDocument();
expect(getByText('Juggling')).toBeInTheDocument();
expect(getByText('Product')).toBeInTheDocument();
expect(getByText('Data Science')).toBeInTheDocument();
expect(getByText('jane@datahub.ui')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,43 @@
import React from 'react';
import { render } from '@testing-library/react';
import UserOwnership from '../UserOwnership';
import { EntityType, PlatformNativeType } from '../../../../types.generated';
import TestPageContainer from '../../../../utils/test-utils/TestPageContainer';
const ownerships = {
[EntityType.Dataset]: [
{
name: 'HiveDataset',
origin: 'PROD',
description: 'this is a dataset',
platformNativeType: PlatformNativeType.Table,
},
{
name: 'KafkaDataset',
origin: 'PROD',
description: 'this is also a dataset',
platformNativeType: PlatformNativeType.Table,
},
],
};
describe('UserOwnership', () => {
it('renders a list container', () => {
const { getByText } = render(
<TestPageContainer>
<UserOwnership ownerships={ownerships} entityPath="dataset" />
</TestPageContainer>,
);
expect(getByText('Datasets they own')).toBeInTheDocument();
});
it('renders the entity rows', () => {
const { getByText } = render(
<TestPageContainer>
<UserOwnership ownerships={ownerships} entityPath="dataset" />
</TestPageContainer>,
);
expect(getByText('this is a dataset')).toBeInTheDocument();
expect(getByText('this is also a dataset')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,35 @@
import { createBrowserHistory } from 'history';
import { getTestEntityRegistry } from '../../../../../utils/test-utils/TestPageContainer';
import { Subview } from '../../Subview';
import { navigateToUserUrl } from '../navigateToUserUrl';
const history = createBrowserHistory();
const entityRegistry = getTestEntityRegistry();
describe('navigateToUserUrl', () => {
beforeEach(() => {
jest.spyOn(history, 'push');
});
it('navigates to the correct url without subviews', () => {
navigateToUserUrl({
urn: 'test:urn',
history,
entityRegistry,
});
expect(history.push).toHaveBeenCalledWith({ pathname: '/user/test:urn' });
});
it('navigates to the correct url with subviews', () => {
navigateToUserUrl({
urn: 'test:urn',
subview: Subview.Ownership,
item: 'dataset',
history,
entityRegistry,
});
expect(history.push).toHaveBeenCalledWith({ pathname: '/user/test:urn/ownership/dataset' });
});
});

View File

@ -0,0 +1,23 @@
import { RouteComponentProps } from 'react-router-dom';
import { EntityType } from '../../../../types.generated';
import EntityRegistry from '../../EntityRegistry';
export const navigateToUserUrl = ({
urn,
subview,
item,
history,
entityRegistry,
}: {
urn: string;
subview?: string;
item?: string;
history: RouteComponentProps['history'];
entityRegistry: EntityRegistry;
}) => {
history.push({
pathname: `/${entityRegistry.getPathName(EntityType.User)}/${urn}${subview ? `/${subview}` : ''}${
item && subview ? `/${item}` : ''
}`,
});
};

View File

@ -0,0 +1,27 @@
import { useMemo } from 'react';
import { useLocation, useParams } from 'react-router';
import { Subview } from '../Subview';
type UserPageParams = {
urn: string;
};
const SUBVIEW_PATH_INDEX = 3;
const ITEM_PATH_INDEX = 4;
export default function useUserParams(): { subview?: Subview; item?: string; urn: string } {
const location = useLocation();
const { urn } = useParams<UserPageParams>();
return useMemo(() => {
const parts = location.pathname.split('/');
const subview = parts[SUBVIEW_PATH_INDEX];
return {
urn,
subview:
subview && Object.values(Subview).indexOf(subview as Subview) >= 0 ? (subview as Subview) : undefined,
item: parts[ITEM_PATH_INDEX],
};
}, [location.pathname, urn]);
}

View File

@ -14,5 +14,4 @@ export enum PageRoutes {
BROWSE = '/browse',
BROWSE_RESULTS = '/browse/:type',
DATASETS = '/datasets',
USERS = '/users',
}

View File

@ -0,0 +1,28 @@
import React, { useMemo } from 'react';
import { MemoryRouter } from 'react-router';
import { DatasetEntity } from '../../components/entity/dataset/DatasetEntity';
import { UserEntity } from '../../components/entity/user/User';
import EntityRegistry from '../../components/entity/EntityRegistry';
import { EntityRegistryContext } from '../../entityRegistryContext';
type Props = {
children: React.ReactNode;
};
export function getTestEntityRegistry() {
const entityRegistry = new EntityRegistry();
entityRegistry.register(new DatasetEntity());
entityRegistry.register(new UserEntity());
return entityRegistry;
}
export default ({ children }: Props) => {
const entityRegistry = useMemo(() => getTestEntityRegistry(), []);
return (
<MemoryRouter>
<EntityRegistryContext.Provider value={entityRegistry}>{children}</EntityRegistryContext.Provider>;
</MemoryRouter>
);
};

View File

@ -163,7 +163,7 @@
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.10":
"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.10":
version "7.12.10"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz"
integrity sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==
@ -1257,7 +1257,7 @@
globals "^11.1.0"
lodash "^4.17.19"
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5", "@babel/traverse@^7.12.9", "@babel/traverse@^7.7.0":
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5", "@babel/traverse@^7.12.9", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0":
version "7.12.12"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.12.tgz"
integrity sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==
@ -1359,7 +1359,7 @@
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.6":
"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.6", "@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
@ -1405,12 +1405,12 @@
"@emotion/styled-base" "^10.0.27"
babel-plugin-emotion "^10.0.27"
"@emotion/stylis@0.8.5":
"@emotion/stylis@0.8.5", "@emotion/stylis@^0.8.4":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
"@emotion/unitless@0.7.5":
"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
@ -3071,6 +3071,14 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
"@types/hoist-non-react-statics@*":
version "3.3.1"
resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/html-minifier-terser@^5.0.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz"
@ -3322,6 +3330,15 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz"
integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
"@types/styled-components@^5.1.7":
version "5.1.7"
resolved "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.7.tgz"
integrity sha512-BJzPhFygYspyefAGFZTZ/8lCEY4Tk+Iqktvnko3xmJf9LrLqs3+grxPeU3O0zLl6yjbYBopD0/VikbHgXDbJtA==
dependencies:
"@types/hoist-non-react-statics" "*"
"@types/react" "*"
csstype "^3.0.2"
"@types/tapable@*", "@types/tapable@^1.0.5":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz"
@ -4537,6 +4554,16 @@ babel-plugin-react-docgen@^4.1.0, babel-plugin-react-docgen@^4.2.1:
lodash "^4.17.15"
react-docgen "^5.0.0"
"babel-plugin-styled-components@>= 1":
version "1.12.0"
resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz"
integrity sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
"@babel/helper-module-imports" "^7.0.0"
babel-plugin-syntax-jsx "^6.18.0"
lodash "^4.17.11"
babel-plugin-syntax-jsx@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz"
@ -5236,6 +5263,11 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
camelize@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz"
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
caniuse-api@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz"
@ -6017,6 +6049,11 @@ css-blank-pseudo@^0.1.4:
dependencies:
postcss "^7.0.5"
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz"
integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=
css-color-names@0.0.4, css-color-names@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz"
@ -6107,6 +6144,15 @@ css-select@^2.0.0:
domutils "^1.7.0"
nth-check "^1.0.2"
css-to-react-native@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz"
integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
css-tree@1.0.0-alpha.37:
version "1.0.0-alpha.37"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz"
@ -8610,6 +8656,13 @@ history@^4.9.0:
tiny-warning "^1.0.0"
value-equal "^1.0.1"
history@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08"
integrity sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==
dependencies:
"@babel/runtime" "^7.7.6"
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz"
@ -8619,7 +8672,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -13067,7 +13120,7 @@ query-string@*, query-string@^4.1.0:
query-string@^6.13.8:
version "6.13.8"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.8.tgz#8cf231759c85484da3cf05a851810d8e825c1159"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.8.tgz"
integrity sha512-jxJzQI2edQPE/NPUOusNjO/ZOGqr1o2OBa/3M00fU76FsLXDVbJDv/p7ng5OdQyorKrkRz1oqfwmbe5MAMePQg==
dependencies:
decode-uri-component "^0.2.0"
@ -15037,7 +15090,7 @@ spdy@^4.0.2:
split-on-first@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz"
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
split-string@^3.0.1, split-string@^3.0.2:
@ -15175,7 +15228,7 @@ strict-uri-encode@^1.0.0:
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz"
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
string-convert@^0.2.0:
@ -15394,6 +15447,22 @@ style-to-object@0.3.0, style-to-object@^0.3.0:
dependencies:
inline-style-parser "0.1.1"
styled-components@^5.2.1:
version "5.2.1"
resolved "https://registry.npmjs.org/styled-components/-/styled-components-5.2.1.tgz"
integrity sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/traverse" "^7.4.5"
"@emotion/is-prop-valid" "^0.8.8"
"@emotion/stylis" "^0.8.4"
"@emotion/unitless" "^0.7.4"
babel-plugin-styled-components ">= 1"
css-to-react-native "^3.0.0"
hoist-non-react-statics "^3.0.0"
shallowequal "^1.1.0"
supports-color "^5.5.0"
stylehacks@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz"
@ -15408,7 +15477,7 @@ supports-color@^2.0.0:
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
supports-color@^5.3.0:
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==