forked from sombochea/verdaccio-ui
refactor: Developers component
Using React hooks
This commit is contained in:
32
src/components/AvatarTooltip/AvatarTooltip.tsx
Normal file
32
src/components/AvatarTooltip/AvatarTooltip.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { isEmail } from '../../utils/url';
|
||||
|
||||
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, avatarComponent, packageName, version): 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 };
|
||||
4
src/components/AvatarTooltip/index.ts
Normal file
4
src/components/AvatarTooltip/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { AvatarTooltip } from './AvatarTooltip';
|
||||
|
||||
export { AvatarTooltip };
|
||||
export default AvatarTooltip;
|
||||
103
src/components/Developers/Developers.test.tsx
Normal file
103
src/components/Developers/Developers.test.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Developers, { DevelopersType } from './Developers';
|
||||
import { Fab } from './styles';
|
||||
import { DetailContextProvider } from '../../pages/version/Version';
|
||||
|
||||
describe('test Developers', () => {
|
||||
const packageMeta = {
|
||||
latest: {
|
||||
packageName: 'foo',
|
||||
version: '1.0.0',
|
||||
maintainers: [
|
||||
{
|
||||
name: 'dmethvin',
|
||||
email: 'dave.methvin@gmail.com',
|
||||
},
|
||||
{
|
||||
name: 'mgol',
|
||||
email: 'm.goleb@gmail.com',
|
||||
},
|
||||
],
|
||||
contributors: [
|
||||
{
|
||||
name: 'dmethvin',
|
||||
email: 'dave.methvin@gmail.com',
|
||||
},
|
||||
{
|
||||
name: 'mgol',
|
||||
email: 'm.goleb@gmail.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
test('should render the component with no items', () => {
|
||||
const type: DevelopersType = 'maintainers';
|
||||
const packageMeta = {
|
||||
latest: {},
|
||||
};
|
||||
const wrapper = mount(
|
||||
// @ts-ignore
|
||||
<DetailContextProvider value={{ packageMeta }}>
|
||||
<Developers type={type} />
|
||||
</DetailContextProvider>
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the component for maintainers with items', () => {
|
||||
const type: DevelopersType = 'maintainers';
|
||||
const wrapper = mount(
|
||||
// @ts-ignore
|
||||
<DetailContextProvider value={{ packageMeta }}>
|
||||
<Developers type={type} />
|
||||
</DetailContextProvider>
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the component for contributors with items', () => {
|
||||
const type: DevelopersType = 'contributors';
|
||||
const wrapper = mount(
|
||||
// @ts-ignore
|
||||
<DetailContextProvider value={{ packageMeta }}>
|
||||
<Developers type={type} />
|
||||
</DetailContextProvider>
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should test onClick the component avatar', () => {
|
||||
const type: DevelopersType = 'contributors';
|
||||
const packageMeta = {
|
||||
latest: {
|
||||
packageName: 'foo',
|
||||
version: '1.0.0',
|
||||
contributors: [
|
||||
{
|
||||
name: 'dmethvin',
|
||||
email: 'dave.methvin@gmail.com',
|
||||
},
|
||||
{
|
||||
name: 'dmethvin2',
|
||||
email: 'dave2.methvin@gmail.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
// @ts-ignore
|
||||
<DetailContextProvider value={{ packageMeta }}>
|
||||
<Developers type={type} visibleMax={1} />
|
||||
</DetailContextProvider>
|
||||
);
|
||||
|
||||
const item2 = wrapper.find(Fab);
|
||||
item2.simulate('click');
|
||||
});
|
||||
});
|
||||
@@ -1,79 +1,59 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import Add from '@material-ui/icons/Add';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/Version';
|
||||
import { DetailContext } from '../../pages/version/Version';
|
||||
import { AvatarTooltip } from '../AvatarTooltip';
|
||||
import { Details, Heading, Content, Fab } from './styles';
|
||||
import { isEmail } from '../../utils/url';
|
||||
|
||||
export type DevelopersType = 'contributors' | 'maintainers';
|
||||
|
||||
interface Props {
|
||||
type: 'contributors' | 'maintainers';
|
||||
}
|
||||
interface State {
|
||||
visibleDevs: number;
|
||||
type: DevelopersType;
|
||||
visibleMax?: number;
|
||||
}
|
||||
|
||||
class Developers extends Component<Props, State> {
|
||||
public state = {
|
||||
visibleDevs: 6,
|
||||
export const VISIBLE_MAX = 6;
|
||||
|
||||
const Developers: FC<Props> = ({ type, visibleMax }) => {
|
||||
const [visibleDevs, setVisibleDevs] = React.useState<number>(visibleMax || VISIBLE_MAX);
|
||||
const { packageMeta } = React.useContext(DetailContext);
|
||||
|
||||
const handleLoadMore = () => {
|
||||
setVisibleDevs(visibleDevs + VISIBLE_MAX);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{({ packageMeta }) => {
|
||||
const { type } = this.props;
|
||||
const developerType = packageMeta && packageMeta.latest[type];
|
||||
if (!developerType || developerType.length === 0) return null;
|
||||
return this.renderDevelopers(developerType, packageMeta);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
const renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => {
|
||||
const { name: packageName, version } = packageMeta.latest;
|
||||
|
||||
public handleLoadMore = () => {
|
||||
this.setState(prev => ({ visibleDevs: prev.visibleDevs + 6 }));
|
||||
return <AvatarTooltip avatar={avatar} email={email} name={name} packageName={packageName} version={version} />;
|
||||
};
|
||||
|
||||
private renderDevelopers = (developers, packageMeta) => {
|
||||
const { type } = this.props;
|
||||
const { visibleDevs } = this.state;
|
||||
const renderDevelopers = (developers, packageMeta) => {
|
||||
const listVisibleDevelopers = developers.slice(0, visibleDevs);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Fragment>
|
||||
<Heading variant={'subheading'}>{type}</Heading>
|
||||
<Content>
|
||||
{developers.slice(0, visibleDevs).map(developer => (
|
||||
<Details key={developer.email}>{this.renderDeveloperDetails(developer, packageMeta)}</Details>
|
||||
{listVisibleDevelopers.map(developer => (
|
||||
<Details key={developer.email}>{renderDeveloperDetails(developer, packageMeta)}</Details>
|
||||
))}
|
||||
{visibleDevs < developers.length && (
|
||||
<Fab onClick={this.handleLoadMore} size="small">
|
||||
<Fab onClick={handleLoadMore} size="small">
|
||||
<Add />
|
||||
</Fab>
|
||||
)}
|
||||
</Content>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private renderLinkForMail(email, avatarComponent, packageName, version): JSX.Element {
|
||||
if (!email || isEmail(email) === false) {
|
||||
return avatarComponent;
|
||||
}
|
||||
return (
|
||||
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
|
||||
{avatarComponent}
|
||||
</a>
|
||||
);
|
||||
const developerList = packageMeta && packageMeta.latest[type];
|
||||
if (!developerList || developerList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => {
|
||||
const { name: packageName, version } = packageMeta.latest;
|
||||
|
||||
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
|
||||
return <Tooltip title={name}>{this.renderLinkForMail(email, avatarComponent, packageName, version)}</Tooltip>;
|
||||
};
|
||||
}
|
||||
return renderDevelopers(developerList, packageMeta);
|
||||
};
|
||||
|
||||
export default Developers;
|
||||
|
||||
5467
src/components/Developers/__snapshots__/Developers.test.tsx.snap
Normal file
5467
src/components/Developers/__snapshots__/Developers.test.tsx.snap
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import styled from 'react-emotion';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
import { fontWeight } from '../../utils/styles/sizes';
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Footer /> component should load the initial state of Footer component 1`] = `"<div class=\\"css-i0nj2g ezbsl480\\"><div class=\\"css-hzfs9b ezbsl481\\"><div class=\\"css-d8nsp7 ezbsl482\\"> Made with<span class=\\"css-1so4oe0 ezbsl487\\">♥</span>on<span class=\\"css-1ie354y ezbsl484\\"><svg class=\\"ezbsl485 css-1kgp95j ek145dl0\\"><title>Earth</title><use xlink:href=\\"[object Object]#earth\\"></use></svg><span class=\\"css-8631ip ezbsl486\\"><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Spain</title><use xlink:href=\\"[object Object]#spain\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Nicaragua</title><use xlink:href=\\"[object Object]#nicaragua\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>India</title><use xlink:href=\\"[object Object]#india\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Brazil</title><use xlink:href=\\"[object Object]#brazil\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>China</title><use xlink:href=\\"[object Object]#china\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg></span></span></div><div class=\\"css-1wbzdyy ezbsl483\\">Powered by<span class=\\"ezbsl488 css-i15wza ek145dl1\\" name=\\"verdaccio\\" title=\\"Verdaccio\\"><img alt=\\"Verdaccio\\" src=\\"[object Object]\\" class=\\"css-1ncdhax ek145dl2\\"></span>/ v.1.0.0</div></div></div>"`;
|
||||
exports[`<Footer /> component should load the initial state of Footer component 1`] = `"<div class=\\"css-i0nj2g ezbsl480\\"><div class=\\"css-hzfs9b ezbsl481\\"><div class=\\"css-d8nsp7 ezbsl482\\"> Made with<span class=\\"css-1so4oe0 ezbsl487\\">♥</span>on<span class=\\"css-1ie354y ezbsl484\\"><svg class=\\"ezbsl485 css-tsfgle ek145dl0\\"><title>Earth</title><use xlink:href=\\"[object Object]#earth\\"></use></svg><span class=\\"css-8631ip ezbsl486\\"><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Spain</title><use xlink:href=\\"[object Object]#spain\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Nicaragua</title><use xlink:href=\\"[object Object]#nicaragua\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>India</title><use xlink:href=\\"[object Object]#india\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Brazil</title><use xlink:href=\\"[object Object]#brazil\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>China</title><use xlink:href=\\"[object Object]#china\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg></span></span></div><div class=\\"css-1wbzdyy ezbsl483\\">Powered by<span class=\\"ezbsl488 css-i15wza ek145dl1\\" name=\\"verdaccio\\" title=\\"Verdaccio\\"><img alt=\\"Verdaccio\\" src=\\"[object Object]\\" class=\\"css-1ncdhax ek145dl2\\"></span>/ v.1.0.0</div></div></div>"`;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Icon /> component should render the component in default state 1`] = `"<svg class=\\"css-3skwlp ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg>"`;
|
||||
exports[`<Icon /> component should render the component in default state 1`] = `"<svg class=\\"css-snirlv ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg>"`;
|
||||
|
||||
@@ -21,7 +21,7 @@ const getSize = (size: Breakpoint): string => {
|
||||
const commonStyle = ({ size = 'sm' as Breakpoint, pointer, modifiers = null }): string => css`
|
||||
&& {
|
||||
display: inline-block;
|
||||
cursor: ${pointer ? 'pointer' : 'default'};
|
||||
cursor: ${pointer ? 'pointer' : 'Developers'};
|
||||
${getSize(size)};
|
||||
${modifiers && modifiers};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user