Refactor: Updated developers component structure (#360)

This commit is contained in:
Priscila Oliveira 2019-12-17 22:57:53 -03:00 committed by GitHub
parent eef2913dd5
commit 3a9f66c023
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 699 additions and 713 deletions

View File

@ -1,37 +0,0 @@
import React, { FC } from 'react';
import { isEmail } from '../../utils/url';
import Tooltip from '../../muiComponents/Tooltip';
import Avatar from '../../muiComponents/Avatar';
export interface AvatarDeveloper {
name: string;
packageName: string;
version: string;
avatar: string;
email: string;
}
const AvatarTooltip: FC<AvatarDeveloper> = ({ name, packageName, version, avatar, email }) => {
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
function renderLinkForMail(
email: string,
avatarComponent: JSX.Element,
packageName: string,
version: string
): JSX.Element {
if (!email || isEmail(email) === false) {
return avatarComponent;
}
return (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
);
}
return <Tooltip title={name}>{renderLinkForMail(email, avatarComponent, packageName, version)}</Tooltip>;
};
export { AvatarTooltip };

View File

@ -1,4 +0,0 @@
import { AvatarTooltip } from './AvatarTooltip';
export { AvatarTooltip };
export default AvatarTooltip;

View File

@ -2,7 +2,7 @@ import React, { ReactElement } from 'react';
import ActionBar from '../ActionBar'; import ActionBar from '../ActionBar';
import Author from '../Author'; import Author from '../Author';
import Developers from '../Developers'; import Developers, { DeveloperType } from '../Developers';
import Dist from '../Dist/Dist'; import Dist from '../Dist/Dist';
import Engine from '../Engines/Engines'; import Engine from '../Engines/Engines';
import Install from '../Install'; import Install from '../Install';
@ -28,8 +28,6 @@ const renderLatestDescription = (description, version, isLatest = true): JSX.Ele
}; };
const renderCopyCLI = (): JSX.Element => <Install />; const renderCopyCLI = (): JSX.Element => <Install />;
const renderMaintainers = (): JSX.Element => <Developers type="maintainers" />;
const renderContributors = (): JSX.Element => <Developers type="contributors" />;
const renderRepository = (): JSX.Element => <Repository />; const renderRepository = (): JSX.Element => <Repository />;
const renderAuthor = (): JSX.Element => <Author />; const renderAuthor = (): JSX.Element => <Author />;
const renderEngine = (): JSX.Element => <Engine />; const renderEngine = (): JSX.Element => <Engine />;
@ -63,8 +61,8 @@ function renderSideBar(packageName, packageVersion, packageMeta): ReactElement<H
{renderEngine()} {renderEngine()}
{renderDist()} {renderDist()}
{renderAuthor()} {renderAuthor()}
{renderMaintainers()} <Developers type={DeveloperType.MAINTAINERS} />
{renderContributors()} <Developers type={DeveloperType.CONTRIBUTORS} />
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View File

@ -3,8 +3,7 @@ import React from 'react';
import { mount } from '../../utils/test-enzyme'; import { mount } from '../../utils/test-enzyme';
import { DetailContextProvider } from '../../pages/Version'; import { DetailContextProvider } from '../../pages/Version';
import Developers, { DevelopersType } from './Developers'; import Developers, { DeveloperType, Fab } from './Developers';
import { Fab } from './styles';
describe('test Developers', () => { describe('test Developers', () => {
const packageMeta = { const packageMeta = {
@ -35,14 +34,13 @@ describe('test Developers', () => {
}; };
test('should render the component with no items', () => { test('should render the component with no items', () => {
const type: DevelopersType = 'maintainers';
const packageMeta = { const packageMeta = {
latest: {}, latest: {},
}; };
const wrapper = mount( const wrapper = mount(
// @ts-ignore // @ts-ignore
<DetailContextProvider value={{ packageMeta }}> <DetailContextProvider value={{ packageMeta }}>
<Developers type={type} /> <Developers type={DeveloperType.MAINTAINERS} />
</DetailContextProvider> </DetailContextProvider>
); );
@ -50,11 +48,10 @@ describe('test Developers', () => {
}); });
test('should render the component for maintainers with items', () => { test('should render the component for maintainers with items', () => {
const type: DevelopersType = 'maintainers';
const wrapper = mount( const wrapper = mount(
// @ts-ignore // @ts-ignore
<DetailContextProvider value={{ packageMeta }}> <DetailContextProvider value={{ packageMeta }}>
<Developers type={type} /> <Developers type={DeveloperType.MAINTAINERS} />
</DetailContextProvider> </DetailContextProvider>
); );
@ -62,11 +59,10 @@ describe('test Developers', () => {
}); });
test('should render the component for contributors with items', () => { test('should render the component for contributors with items', () => {
const type: DevelopersType = 'contributors';
const wrapper = mount( const wrapper = mount(
// @ts-ignore // @ts-ignore
<DetailContextProvider value={{ packageMeta }}> <DetailContextProvider value={{ packageMeta }}>
<Developers type={type} /> <Developers type={DeveloperType.CONTRIBUTORS} />
</DetailContextProvider> </DetailContextProvider>
); );
@ -74,7 +70,6 @@ describe('test Developers', () => {
}); });
test('should test onClick the component avatar', () => { test('should test onClick the component avatar', () => {
const type: DevelopersType = 'contributors';
const packageMeta = { const packageMeta = {
latest: { latest: {
packageName: 'foo', packageName: 'foo',
@ -95,7 +90,7 @@ describe('test Developers', () => {
const wrapper = mount( const wrapper = mount(
// @ts-ignore // @ts-ignore
<DetailContextProvider value={{ packageMeta }}> <DetailContextProvider value={{ packageMeta }}>
<Developers type={type} visibleMax={1} /> <Developers type={DeveloperType.CONTRIBUTORS} visibleMax={1} />
</DetailContextProvider> </DetailContextProvider>
); );

View File

@ -1,60 +1,89 @@
import React, { FC, Fragment } from 'react'; import React, { useState, useCallback, useContext, useEffect, useMemo } from 'react';
import Add from '@material-ui/icons/Add'; import Add from '@material-ui/icons/Add';
import styled from '@emotion/styled';
import { DetailContext } from '../../pages/Version'; import { DetailContext } from '../../pages/Version';
import { AvatarTooltip } from '../AvatarTooltip'; import Tooltip from '../../muiComponents/Tooltip';
import Avatar from '../../muiComponents/Avatar';
import Box from '../../muiComponents/Box';
import Text from '../../muiComponents/Text';
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
import { Theme } from '../../design-tokens/theme';
import { Details, StyledText, Content, Fab } from './styles'; import getUniqueDeveloperValues from './get-unique-developer-values';
export type DevelopersType = 'contributors' | 'maintainers'; export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
backgroundColor: props.theme && props.theme.palette.primary.main,
color: props.theme && props.theme.palette.white,
}));
export enum DeveloperType {
CONTRIBUTORS = 'contributors',
MAINTAINERS = 'maintainers',
}
interface Props { interface Props {
type: DevelopersType; type: DeveloperType;
visibleMax?: number; visibleMax?: number;
} }
export const StyledText = styled(Text)<{ theme?: Theme }>(({ theme }) => ({
fontWeight: theme && theme.fontWeight.bold,
marginBottom: '10px',
textTransform: 'capitalize',
}));
const StyledBox = styled(Box)({
'> *': {
margin: 5,
},
});
export const VISIBLE_MAX = 6; export const VISIBLE_MAX = 6;
const Developers: FC<Props> = ({ type, visibleMax }) => { const Developers: React.FC<Props> = ({ type, visibleMax = VISIBLE_MAX }) => {
const [visibleDevs, setVisibleDevs] = React.useState<number>(visibleMax || VISIBLE_MAX); const detailContext = useContext(DetailContext);
const { packageMeta } = React.useContext(DetailContext);
const handleLoadMore = (): void => { if (!detailContext) {
setVisibleDevs(visibleDevs + VISIBLE_MAX); throw Error("The app's detail Context was not correct used");
};
const renderDeveloperDetails = ({ name, avatar, email }, packageMeta): JSX.Element => {
const { name: packageName, version } = packageMeta.latest;
return <AvatarTooltip avatar={avatar} email={email} name={name} packageName={packageName} version={version} />;
};
const renderDevelopers = (developers, packageMeta): JSX.Element => {
const listVisibleDevelopers = developers.slice(0, visibleDevs);
return (
<Fragment>
<StyledText variant={'subtitle1'}>{type}</StyledText>
<Content>
{listVisibleDevelopers.map(developer => (
<Details key={developer.email}>{renderDeveloperDetails(developer, packageMeta)}</Details>
))}
{visibleDevs < developers.length && (
<Fab onClick={handleLoadMore} size="small">
<Add />
</Fab>
)}
</Content>
</Fragment>
);
};
const developerList = packageMeta && packageMeta.latest[type];
if (!developerList || developerList.length === 0) {
return null;
} }
return renderDevelopers(developerList, packageMeta); const developers = useMemo(() => getUniqueDeveloperValues(detailContext.packageMeta?.latest[type]), [
detailContext.packageMeta,
type,
]);
const [visibleDevelopersMax, setVisibleDevelopersMax] = useState(visibleMax);
const [visibleDevelopers, setVisibleDevelopers] = useState(developers);
useEffect(() => {
if (!developers) return;
setVisibleDevelopers(developers.slice(0, visibleDevelopersMax));
}, [developers, visibleDevelopersMax]);
const handleSetVisibleDevelopersMax = useCallback(() => {
setVisibleDevelopersMax(visibleDevelopersMax + VISIBLE_MAX);
}, [visibleDevelopersMax]);
if (!visibleDevelopers || !developers) return null;
return (
<>
<StyledText variant={'subtitle1'}>{type}</StyledText>
<StyledBox display="flex" flexWrap="wrap" margin="10px 0 10px 0">
{visibleDevelopers.map(visibleDeveloper => (
<Tooltip key={visibleDeveloper.email} title={visibleDeveloper.name}>
<Avatar alt={visibleDeveloper.name} src={visibleDeveloper.avatar} />
</Tooltip>
))}
{visibleDevelopersMax < developers.length && (
<Fab onClick={handleSetVisibleDevelopersMax} size="small">
<Add />
</Fab>
)}
</StyledBox>
</>
);
}; };
export default Developers; export default Developers;

View File

@ -0,0 +1,12 @@
import { Developer } from '../../../types/packageMeta';
function getUniqueDeveloperValues(developers?: Array<Developer>): undefined | Array<Developer> {
if (!developers) return;
return developers.reduce(
(accumulator: Array<Developer>, current: Developer) =>
accumulator.some(developer => developer.email === current.email) ? accumulator : [...accumulator, current],
[]
);
}
export default getUniqueDeveloperValues;

View File

@ -1 +1 @@
export { default } from './Developers'; export { default, DeveloperType } from './Developers';

View File

@ -24,9 +24,16 @@ export interface PackageMetaInterface {
type?: string; type?: string;
url?: string; url?: string;
}; };
maintainers?: Array<Developer>;
contributors?: Array<Developer>;
}; };
_uplinks: {}; _uplinks: {};
} }
export interface Developer {
name: string;
email: string;
avatar: string;
}
interface LicenseInterface { interface LicenseInterface {
type: string; type: string;