Skip to content

Commit

Permalink
#70 Added UI sagas and hook.
Browse files Browse the repository at this point in the history
  • Loading branch information
artzub committed Dec 28, 2020
1 parent 48576e2 commit 8a24669
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 10 deletions.
8 changes: 6 additions & 2 deletions src/components/Header/User.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ const Container = styled.button`
&:hover {
background: rgba(255, 255, 255, 0.1);
}
&:active {
background: rgba(255, 255, 255, 0.2);
}
`;

const InfoContainer = styled.div`
Expand Down Expand Up @@ -75,11 +79,11 @@ const PropertyValue = styled.div`
margin-left: 2px;
`;

const User = () => {
const User = (props) => {
const { profile } = useSelector(slice.selectors.getState);

return (
<Container>
<Container {...props}>
<Avatar src={profile?.avatar_url} />
<InfoContainer>
{!profile && <div>Find a user</div>}
Expand Down
19 changes: 17 additions & 2 deletions src/components/Header/UserSearch.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import slice from '@/redux/modules/profiles';
import Highlight from "@/shared/components/Highlight";
import LoadingOverlay from "@/shared/components/LoadingOverlay";
import ScrollBar from "@/shared/components/ScrollBar";
import { useUIProperty } from "@/shared/hooks";
import {
Avatar, ListItem as ListItemOrigin,
ListItemAvatar, ListSubheader,
Expand Down Expand Up @@ -62,9 +63,11 @@ const TopHeader = (

const UserSearch = () => {
const dispatch = useDispatch();
const inputRef = useRef();
const [search, setSearch] = useState('');
const [neverChange, setNeverChange] = useState(true);
const { isFetching, searched, top } = useSelector(slice.selectors.getState);
const [bodyOpen, setBodyOpen] = useUIProperty('bodyOpen');

const onChange = useCallback(
(event) => {
Expand All @@ -77,9 +80,10 @@ const UserSearch = () => {
const onClick = useCallback(
(user) => () => {
dispatch(slice.actions.setProfile(user));
setBodyOpen(false);
dispatch(slice.actions.fetchProfile(user.login));
},
[dispatch],
[setBodyOpen, dispatch],
);

useDebounce(
Expand All @@ -96,12 +100,23 @@ const UserSearch = () => {
[search, dispatch],
);

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

return (
<Container>
<TextField
label="Username"
placeholder="Type username"
onChange={onChange}
ref={inputRef}
/>
<LoadingOverlay loading={isFetching}>
<ListContainer>
Expand Down
31 changes: 25 additions & 6 deletions src/components/Header/index.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import FetchTopUser from "@/components/Header/FetchTopUser";
import User from "@/components/Header/User";
import UserSearch from "@/components/Header/UserSearch";
import { StageTypes } from "@/models/StageTypes";
import { useUIProperty } from "@/shared/hooks";
import Collapse from "@material-ui/core/Collapse";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import { withStyles } from "@material-ui/core/styles";
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";


const PaperStyled = withStyles(() => ({
root: {
position: 'absolute',
Expand All @@ -19,13 +20,29 @@ const PaperStyled = withStyles(() => ({
},
}))(Paper);

const StepBodies = {
[StageTypes.user]: UserSearch,
};

const Header = () => {
const [step, setStep] = useUIProperty('step');
const [bodyOpen, setBodyOpen] = useUIProperty('bodyOpen');
const [value, setValue] = useState(0);

const handleChange = (event, newValue) => {
setValue(newValue);
};

const StepBody = StepBodies[step];

const onClick = useCallback(
(newStep) => () => {
setStep(newStep);
setBodyOpen((prev) => !prev);
},
[setBodyOpen, setStep],
);

return (
<PaperStyled square>
<FetchTopUser />
Expand All @@ -42,17 +59,19 @@ const Header = () => {
<Tab label="Show" />
</Tabs>
<Grid container>
<User />
<User onClick={onClick(StageTypes.user)} />
<div>
Repository
</div>
<div>
Show
</div>
</Grid>
<Collapse in>
<UserSearch />
</Collapse>
{StepBody && (
<Collapse in={bodyOpen}>
<StepBody />
</Collapse>
)}
</PaperStyled>
);
};
Expand Down
5 changes: 5 additions & 0 deletions src/models/StageTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const StageTypes = {
user: 'user',
repository: 'repository',
show: 'show',
};
3 changes: 3 additions & 0 deletions src/redux/modules/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { all } from 'redux-saga/effects';
import profiles from "./profiles";
import ui from './ui';

// Put modules that have their reducers nested in other (root) reducers here
const nestedSlices = [];

// Put modules whose reducers you want in the root tree in this array.
const rootSlices = [
profiles,
ui,
];

const sagas = [...rootSlices, ...nestedSlices]
.map((slice) => slice.sagas)
.filter(Boolean)
.reduce((acc, sagas) => [...acc, ...sagas]);

export function* rootSaga() {
Expand Down
21 changes: 21 additions & 0 deletions src/redux/modules/ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { StageTypes } from "@/models/StageTypes";
import { createSlice } from "@/redux/utils";

const initialState = {
step: StageTypes.user,
view: StageTypes.repository,
bodyOpen: false,
};

export default createSlice({
name: 'ui',
initialState,
reducers: {
change: (state, { payload }) => {
return {
...state,
...payload,
};
},
},
});
1 change: 1 addition & 0 deletions src/shared/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useUIProperty';
31 changes: 31 additions & 0 deletions src/shared/hooks/useUIProperty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useCallback, useRef } from "react";
import slice from '@/redux/modules/ui';
import { functionalUpdate } from "@/shared/utils/functionalUpdate";
import { useDispatch, useSelector } from "react-redux";

/**
* @param {String} property
* @param {*} [defaultValue]
* @return {[*, (function(*): void)]}
*/
export const useUIProperty = (property, defaultValue) => {
const dispatch = useDispatch();
const prop = useRef(null);

prop.current = property || null;
const { [property]: value = defaultValue } = useSelector(slice.selectors.getState);

const prev = useRef(null);
prev.current = value;

const change = useCallback(
(newValue) => {
dispatch(slice.actions.change({
[prop.current]: functionalUpdate(newValue, prev.current),
}));
},
[dispatch],
);

return [value, change];
};
8 changes: 8 additions & 0 deletions src/shared/utils/functionalUpdate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @param {*|function(prevValue): newValue} updater
* @param {*} prev
* @return {*}
*/
export const functionalUpdate = (updater, prev) => {
return typeof updater === 'function' ? updater(prev) : updater;
};
1 change: 1 addition & 0 deletions src/shared/utils/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './makeId';
export * from './functionalUpdate';

0 comments on commit 8a24669

Please sign in to comment.