Skip to content

Commit

Permalink
#68 Added RepositoryStep
Browse files Browse the repository at this point in the history
  • Loading branch information
artzub committed Dec 29, 2020
1 parent 5a028d3 commit 0d849fa
Show file tree
Hide file tree
Showing 6 changed files with 361 additions and 37 deletions.
173 changes: 173 additions & 0 deletions src/components/Header/components/RepositoryStep/Body.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import slice from '@/redux/modules/repositories';
import Highlight from "@/shared/components/Highlight";
import LoadingOverlay from "@/shared/components/LoadingOverlay";
import { ScrollBarMixin } from "@/shared/components/ScrollBar";
import { useUIProperty } from "@/shared/hooks";
import {
Avatar, ListItem as ListItemOrigin,
ListItemAvatar, ListSubheader, TextField,
} from "@material-ui/core";
import List from "@material-ui/core/List";
import ListItemText from "@material-ui/core/ListItemText";
import debounce from 'lodash.debounce';
import SourceRepositoryIcon from "mdi-react/SourceRepositoryIcon";
import { useDispatch, useSelector } from "react-redux";
import { useDebounce } from "react-use";
import { FixedSizeList } from 'react-window';
import styled from "styled-components";
import Secondary from "./Secondary";

const Container = styled.div`
min-height: 100px;
display: flex;
flex-direction: column;
padding: 10px;
box-sizing: border-box;
width: 100%;
`;

const ListItems = styled(FixedSizeList)`
height: 300px !important;
${ScrollBarMixin}
`;

const ListItem = styled(ListItemOrigin)`
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(255, 255, 255, 0.1);
}
&:active {
background: rgba(255, 255, 255, 0.2);
}
`;

const NotData = styled(({ className }) => (
<div className={className}>
<div>Repositories not found</div>
</div>
))`
display: flex;
justify-content: center;
align-items: center;
`;

const bySearch = (search) => (item) => {
return item?.name?.includes(search);
};

const Body = () => {
const dispatch = useDispatch();
const inputRef = useRef();
const [search, setSearch] = useState('');
const { isFetching, items } = useSelector(slice.selectors.getState);
const [bodyOpen, setBodyOpen] = useUIProperty('bodyOpen');
const [filtered, setFiltered] = useState(items);

const changeSearch = useMemo(
() => debounce(
(value) => setSearch(value),
300,
),
[],
);

const onChange = useCallback(
(event) => {
changeSearch(event.target.value);
},
[changeSearch],
);

const onClick = useCallback(
(item) => () => {
dispatch(slice.actions.setRepository(item));
setBodyOpen(false);
},
[setBodyOpen, dispatch],
);

const ListHeader = useMemo(
() => (
<ListSubheader component="div">
Repositories: {filtered.length || 0} of {items.length || 0}
</ListSubheader>
),
[filtered.length, items.length],
);

const Item = useCallback(
({ index, style }) => {
const item = filtered[index];

return (
<ListItem
alignItems="center"
key={item.name}
onClick={onClick(item)}
style={style}
>
<ListItemAvatar>
<Avatar>
<SourceRepositoryIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={<Highlight search={search} text={item.name} />}
secondary={<Secondary item={item} />}
/>
</ListItem>
);
},
[onClick, search, filtered],
);

useEffect(
() => {
setFiltered(search ? items.filter(bySearch(search)) : items);
},
[items, search, dispatch],
);

useDebounce(
() => {
if (inputRef.current && bodyOpen) {
inputRef.current.querySelector('input').focus();
}
},
100,
[bodyOpen],
);

return (
<Container>
<TextField
label="Repository"
placeholder="Type repository name"
onChange={onChange}
ref={inputRef}
/>
<LoadingOverlay loading={isFetching}>
{/*<ListContainer>*/}
<List
dense
subheader={ListHeader}
>
{!filtered.length && <NotData />}
<ListItems
itemCount={filtered.length}
itemSize={76}
height={300}
>
{Item}
</ListItems>
</List>
{/*</ListContainer>*/}
</LoadingOverlay>
</Container>
);
};

export default Body;
38 changes: 38 additions & 0 deletions src/components/Header/components/RepositoryStep/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import slice from '@/redux/modules/repositories';
import SourceRepositoryIcon from "mdi-react/SourceRepositoryIcon";
import { useSelector } from "react-redux";
import styled from "styled-components";
import Container from '../shared/HeaderContainer';

const InfoContainer = styled.div`
display: flex;
flex-direction: column;
margin-left: 5px;
`;

const Title = styled.div`
font-weight: bold;
display: flex;
flex-wrap: nowrap;
white-space: nowrap;
`;

const Header = (props) => {
const { selected } = useSelector(slice.selectors.getState);
const { name } = selected || {};

return (
<Container {...props}>
<SourceRepositoryIcon />
<InfoContainer>
{!selected && <div>Choice a repository</div>}
{selected && (
<Title>{name}</Title>
)}
</InfoContainer>
</Container>
);
};

export default Header;
100 changes: 100 additions & 0 deletions src/components/Header/components/RepositoryStep/Secondary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from "react";
import GithubEmoji from "@/shared/components/GithubEmoje";
import capitalize from "@material-ui/core/utils/capitalize";
import AlertCircleOutlineIcon from "mdi-react/AlertCircleOutlineIcon";
import CodeTagsIcon from "mdi-react/CodeTagsIcon";
import EyeOutlineIcon from "mdi-react/EyeOutlineIcon";
import SourceForkIcon from "mdi-react/SourceForkIcon";
import StarIcon from "mdi-react/StarIcon";
import PropTypes from 'prop-types';
import styled from "styled-components";

const SecondaryContainer = styled.span`
display: flex;
flex-direction: column;
`;

const Description = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;

const Properties = styled.span`
display: flex;
align-items: center;
font-size: 0.8em;
`;

const Property = styled.span`
display: flex;
align-items: center;
margin-left: 5px;
padding-right: 5px;
border-right: 1px solid rgba(255, 255, 255, 0.2);
&:first-child {
margin-left: 0;
}
&:last-child {
border-right: 0;
padding-right: 0;
}
color: #fff;
`;

const PropertyValue = styled.span`
margin-left: 5px;
`;

const Secondary = ({ item }) => (
<SecondaryContainer>
{item.description && (
<Description title={item.description}>
<GithubEmoji text={item.description} />
</Description>
)}
<Properties>
{item.language && (
<Property title="Forks">
<CodeTagsIcon size={16} />
<PropertyValue>
{capitalize(item.language)}
</PropertyValue>
</Property>
)}
<Property title="Forks">
<SourceForkIcon size={16} />
<PropertyValue>
{item.forks}
</PropertyValue>
</Property>
<Property title="Stars">
<StarIcon size={16} />
<PropertyValue>
{item.stargazers_count}
</PropertyValue>
</Property>
<Property title="Watchers">
<EyeOutlineIcon size={16} />
<PropertyValue>
{item.watchers}
</PropertyValue>
</Property>
<Property title="Opened issues">
<AlertCircleOutlineIcon size={16} />
<PropertyValue>
{item.open_issues}
</PropertyValue>
</Property>
</Properties>
</SecondaryContainer>
);

Secondary.propTypes = {
item: PropTypes.shape().isRequired,
};

export default Secondary;
35 changes: 4 additions & 31 deletions src/components/Header/components/UserStep/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,7 @@ import LinkVariantIcon from "mdi-react/LinkVariantIcon";
import SourceRepositoriesIcon from "mdi-react/SourceRepositoriesIcon";
import { useSelector } from "react-redux";
import styled from "styled-components";

const Container = styled.button`
display: flex;
box-sizing: border-box;
padding: 5px;
font-size: 0.8em;
align-items: center;
flex: 1 1 0;
background: transparent;
border: 0;
border-radius: 0;
color: inherit;
cursor: pointer;
transition: background 0.3s;
outline: 0;
&:focus-visible {
outline: -webkit-focus-ring-color auto 1px;
}
&:hover {
background: rgba(255, 255, 255, 0.1);
}
&:active {
background: rgba(255, 255, 255, 0.2);
}
`;
import HeaderContainer from '../shared/HeaderContainer';

const InfoContainer = styled.div`
display: flex;
Expand Down Expand Up @@ -72,6 +44,7 @@ const Property = styled.div`
}
&:last-child {
border-right: 0;
padding-right: 0;
}
`;

Expand All @@ -87,7 +60,7 @@ const Header = (props) => {
} = selected || {};

return (
<Container {...props}>
<HeaderContainer {...props}>
<Avatar src={avatar_url} />
<InfoContainer>
{!selected && <div>Find a user</div>}
Expand Down Expand Up @@ -119,7 +92,7 @@ const Header = (props) => {
</React.Fragment>
)}
</InfoContainer>
</Container>
</HeaderContainer>
);
};

Expand Down
Loading

0 comments on commit 0d849fa

Please sign in to comment.