Refactored components code

This commit is contained in:
Dm1tr1y147 2020-09-19 21:52:02 +05:00
parent 64340c4ef0
commit c50ad9f2b9
12 changed files with 273 additions and 205 deletions

View File

@ -3,7 +3,11 @@ import { IData } from '../types';
import './main.css'; import './main.css';
const Card = ({ data }: { data: IData }) => ( type props = {
data: IData;
};
const Card: React.FC<props> = ({ data }) => (
<a <a
className="card" className="card"
href={'/' + data.image.slice(data.image.indexOf('media'))} href={'/' + data.image.slice(data.image.indexOf('media'))}

23
src/Header/animations.ts Normal file
View File

@ -0,0 +1,23 @@
import { Transition, Variants } from 'framer-motion';
const headerVariants: Variants = {
expanded: {
height: '100vh'
},
collapsed: {
height: '10vh'
}
};
const headerTransition: Transition = {
duration: 0.5,
ease: 'linear'
};
const loadingLogoTransition: Transition = {
loop: Infinity,
ease: 'linear',
duration: 3
};
export { headerVariants, headerTransition, loadingLogoTransition };

View File

@ -1,95 +1,43 @@
import React, { Dispatch, SetStateAction, useEffect } from 'react'; import React, { Dispatch, SetStateAction } from 'react';
import { motion, Transition } from 'framer-motion'; import { motion } from 'framer-motion';
import { useHistory } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { IFilterQuery, ILoadingState } from '../types'; import { IFilterQuery, ILoadingState } from '../types';
import './main.css'; import './main.css';
import Logotype from '../Logotype'; import Logotype from '../Logotype';
import { genName } from './utils';
import {
headerTransition,
headerVariants,
loadingLogoTransition
} from './animations';
const genName = ( type props = {
searchQuery: IFilterQuery,
path: string,
error: string
): string => {
if (error) {
return error.toString();
}
if (path === '/list' && searchQuery) {
let result = '';
if (searchQuery.class_num) {
result = result + searchQuery.class_num + ' класс';
}
if (searchQuery.predmet_type) {
result = result + ' ' + searchQuery.predmet_type;
}
if (searchQuery.teacher) {
result = result + ' ' + searchQuery.teacher;
}
if (searchQuery.search) {
result = result + ' поиск по "' + searchQuery.search + '"';
}
return result;
}
if (path === '/') {
return 'Банк семинаров';
}
return '';
};
const Header = ({
query,
loading,
setSearchQuery
}: {
query: IFilterQuery; query: IFilterQuery;
loading: ILoadingState; loading: ILoadingState;
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>; setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
}) => { };
const Header: React.FC<props> = ({ query, loading, setSearchQuery }) => {
/* /*
* Hooks definitions * Hooks definitions
*/ */
const history = useHistory(); const { pathname } = useLocation();
useEffect(() => {}, [loading]);
/*
* Animations
*/
const headerVariants = {
expanded: {
height: '100vh'
},
collapsed: {
height: '10vh'
}
};
const transition: Transition = {
duration: 0.5,
ease: 'linear'
};
return ( return (
<motion.header <motion.header
id="name" id="name"
variants={headerVariants} variants={headerVariants}
transition={transition} transition={headerTransition}
animate={loading.fetching ? 'expanded' : 'collapsed'} animate={loading.fetching ? 'expanded' : 'collapsed'}
initial="expanded" initial="expanded"
> >
<h1>{genName(query, history.location.pathname, loading.error)}</h1> <h1>{genName(query, pathname, loading.error)}</h1>
{loading.fetching ? ( {loading.fetching ? (
<motion.div <motion.div
id="loadingLogo" id="loadingLogo"
animate={{ rotate: 360 }} animate={{ rotate: 360 }}
transition={{ loop: Infinity, ease: 'linear', duration: 3 }} transition={loadingLogoTransition}
> >
<Logotype setSearchQuery={setSearchQuery} /> <Logotype setSearchQuery={setSearchQuery} />
</motion.div> </motion.div>

40
src/Header/utils.ts Normal file
View File

@ -0,0 +1,40 @@
import { IFilterQuery } from '../types';
const genName = (
searchQuery: IFilterQuery,
path: string,
error: string
): string => {
if (error) {
return error.toString();
}
if (path === '/list' && searchQuery) {
let result = '';
if (searchQuery.class_num) {
result = result + searchQuery.class_num + ' класс';
}
if (searchQuery.predmet_type) {
result = result + ' ' + searchQuery.predmet_type;
}
if (searchQuery.teacher) {
result = result + ' ' + searchQuery.teacher;
}
if (searchQuery.search) {
result = result + ' поиск по "' + searchQuery.search + '"';
}
return result;
}
if (path === '/') {
return 'Банк семинаров';
}
return '';
};
export { genName };

20
src/Home/handlers.ts Normal file
View File

@ -0,0 +1,20 @@
import { Dispatch, SetStateAction } from 'react';
import { IFilterQuery } from '../types';
const handleShowMore = (
predmet_type: string,
class_num: number,
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>
) => {
setSearchQuery(
(prev): IFilterQuery => {
return {
...prev,
predmet_type,
class_num: class_num.toString()
};
}
);
};
export { handleShowMore };

View File

@ -4,15 +4,15 @@ import { Link } from 'react-router-dom';
import Card from '../Card'; import Card from '../Card';
import NothingFound from '../NothingFound'; import NothingFound from '../NothingFound';
import { IData, IFilterQuery, ILoadingState } from '../types'; import { IData, IFilterQuery, ILoadingState } from '../types';
import { handleShowMore } from './handlers';
import './main.css'; import './main.css';
const Home = ({ type props = {
setLoading,
setSearchQuery
}: {
setLoading: Dispatch<SetStateAction<ILoadingState>>; setLoading: Dispatch<SetStateAction<ILoadingState>>;
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>; setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
}) => { };
const Home: React.FC<props> = ({ setLoading, setSearchQuery }) => {
const [data, setData] = useState<IData[]>([]); const [data, setData] = useState<IData[]>([]);
useEffect(() => { useEffect(() => {
@ -38,18 +38,6 @@ const Home = ({
...Array.from(new Set(data.map((el) => el.predmet_type).sort())) ...Array.from(new Set(data.map((el) => el.predmet_type).sort()))
]; ];
const handleShowMore = (predmet_type: string, class_num: number) => {
setSearchQuery(
(prev): IFilterQuery => {
return {
...prev,
predmet_type,
class_num: class_num.toString()
};
}
);
};
return ( return (
<main className="homeContainer"> <main className="homeContainer">
{classes.length ? ( {classes.length ? (
@ -87,7 +75,8 @@ const Home = ({
onClick={() => onClick={() =>
handleShowMore( handleShowMore(
subject, subject,
class_num class_num,
setSearchQuery
) )
} }
to={'/list'} to={'/list'}

View File

@ -4,13 +4,13 @@ import { Link } from 'react-router-dom';
import { emptyQuery } from '../Navbar/utils'; import { emptyQuery } from '../Navbar/utils';
import { IFilterQuery } from '../types'; import { IFilterQuery } from '../types';
import LogoImage from './logo.png'; import LogoImage from './logo.png';
import './main.css' import './main.css';
const Logotype = ({ type props = {
setSearchQuery
}: {
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>; setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
}) => { };
const Logotype: React.FC<props> = ({ setSearchQuery }) => {
return ( return (
<Link <Link
to="/" to="/"

45
src/Navbar/animations.ts Normal file
View File

@ -0,0 +1,45 @@
import { Transition, Variants } from 'framer-motion'
const searchVariants: Variants = {
open: {
width: 'calc(100vw - 4vh)',
display: 'block'
},
closed: {
width: '6vh',
transitionEnd: {
display: 'none'
}
}
};
const navVariants: Variants = {
open: {
height: '100vh',
borderTopLeftRadius: 0,
borderTopRightRadius: 0
},
closed: {
height: '10vh',
borderTopLeftRadius: '20px',
borderTopRightRadius: '20px'
}
};
const filtersVariants: Variants = {
open: {
height: '100vh',
padding: '2vh'
},
closed: {
height: 0,
padding: 0
}
};
const transition: Transition = {
ease: 'easeIn',
duration: 0.5
};
export { filtersVariants, navVariants, searchVariants, transition };

49
src/Navbar/handlers.ts Normal file
View File

@ -0,0 +1,49 @@
import { Dispatch, RefObject, SetStateAction } from 'react';
import { IFilterQuery } from '../types';
const handleFiltersButton = (
filtersCollapsed: boolean,
localFilters: Partial<IFilterQuery>,
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>,
setFiltersCollapsed: Dispatch<SetStateAction<boolean>>
) => {
if (!filtersCollapsed) {
setSearchQuery((prev) => {
return { ...prev, ...localFilters };
});
}
setFiltersCollapsed((prev) => !prev);
};
const handleSelectChange = (
element: HTMLSelectElement,
setLocalFilters: Dispatch<SetStateAction<Partial<IFilterQuery>>>
) => {
setLocalFilters((prev) => {
return { ...prev, [element.name]: element.value };
});
};
const handleSearchButton = (
searchCollapsed: boolean,
setSearchCollapsed: Dispatch<SetStateAction<boolean>>,
setInputFocus: () => void,
searchInputRef: RefObject<HTMLInputElement>,
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>
) => {
if (searchCollapsed) {
setSearchCollapsed(false);
setInputFocus();
} else if (searchInputRef && searchInputRef.current) {
const value = searchInputRef.current.value;
setSearchQuery((prev) => {
return { ...prev, search: value };
});
setSearchCollapsed(true);
searchInputRef.current.value = '';
}
};
export { handleFiltersButton, handleSelectChange, handleSearchButton };

View File

@ -1,5 +1,4 @@
import React, { import React, {
ChangeEvent,
Dispatch, Dispatch,
SetStateAction, SetStateAction,
useEffect, useEffect,
@ -14,25 +13,31 @@ import SearchIcon from './search.svg';
import { IFilterQuery } from '../types'; import { IFilterQuery } from '../types';
import { useFocus } from '../utils'; import { useFocus } from '../utils';
import Logotype from '../Logotype'; import Logotype from '../Logotype';
import {
filtersVariants,
navVariants,
searchVariants,
transition
} from './animations';
import {
handleFiltersButton,
handleSearchButton,
handleSelectChange
} from './handlers';
const Navbar = ({ type props = {
setSearchQuery,
query
}: {
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>; setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
query: IFilterQuery; query: IFilterQuery;
}) => { };
/*
* Hooks
*/
const Navbar: React.FC<props> = ({ setSearchQuery, query }) => {
const [searchCollapsed, setSearchCollapsed] = useState(true); const [searchCollapsed, setSearchCollapsed] = useState(true);
const [filtersCollapsed, setFiltersCollapsed] = useState(true); const [filtersCollapsed, setFiltersCollapsed] = useState(true);
const [localFilters, setLocalFilters] = useState<Partial<IFilterQuery>>(); const [localFilters, setLocalFilters] = useState<Partial<IFilterQuery>>({});
const searchInput = useRef<HTMLInputElement>(null); const searchInputRef = useRef<HTMLInputElement>(null);
const setInputFocus = useFocus(searchInput); const setInputFocus = useFocus(searchInputRef);
const formRef = useRef<HTMLFormElement>(null); const formRef = useRef<HTMLFormElement>(null);
@ -48,89 +53,6 @@ const Navbar = ({
} }
}, [query]); }, [query]);
/*
* Animations
*/
const searchVariants = {
open: {
width: 'calc(100vw - 4vh)',
display: 'block'
},
closed: {
width: '6vh',
transitionEnd: {
display: 'none'
}
}
};
const navVariants = {
open: {
height: '100vh',
borderTopLeftRadius: 0,
borderTopRightRadius: 0
},
closed: {
height: '10vh',
borderTopLeftRadius: '20px',
borderTopRightRadius: '20px'
}
};
const filtersVariants = {
open: {
height: '100vh',
padding: '2vh'
},
closed: {
height: 0,
padding: 0
}
};
const transition = {
ease: 'easeIn',
duration: 0.5
};
/*
* Input handlers
*/
const handleFiltersButton = () => {
if (!filtersCollapsed) {
setSearchQuery((prev) => {
return { ...prev, ...localFilters };
});
}
setFiltersCollapsed((prev) => !prev);
};
const handleSelectChange = ({
target: element
}: ChangeEvent<HTMLSelectElement>) => {
setLocalFilters((prev) => {
return { ...prev, [element.name]: element.value };
});
};
const handleSearchButton = () => {
if (searchCollapsed) {
setSearchCollapsed(false);
setInputFocus();
} else if (searchInput && searchInput.current) {
const value = searchInput.current.value;
setSearchQuery((prev) => {
return { ...prev, search: value };
});
setSearchCollapsed(true);
searchInput.current.value = '';
}
};
return ( return (
<motion.header <motion.header
id="navbar" id="navbar"
@ -146,7 +68,14 @@ const Navbar = ({
<button <button
className="navButton" className="navButton"
id="filter" id="filter"
onClick={handleFiltersButton} onClick={() =>
handleFiltersButton(
filtersCollapsed,
localFilters,
setSearchQuery,
setFiltersCollapsed
)
}
> >
<img src={FilterIcon} alt="Фильтр" /> <img src={FilterIcon} alt="Фильтр" />
</button> </button>
@ -154,7 +83,15 @@ const Navbar = ({
<button <button
className="navButton" className="navButton"
id="search" id="search"
onClick={handleSearchButton} onClick={() =>
handleSearchButton(
searchCollapsed,
setSearchCollapsed,
setInputFocus,
searchInputRef,
setSearchQuery
)
}
> >
<img src={SearchIcon} alt="Поиск" /> <img src={SearchIcon} alt="Поиск" />
</button> </button>
@ -163,10 +100,16 @@ const Navbar = ({
variants={searchVariants} variants={searchVariants}
transition={transition} transition={transition}
aria-label="Поиск" aria-label="Поиск"
ref={searchInput} ref={searchInputRef}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
handleSearchButton(); handleSearchButton(
searchCollapsed,
setSearchCollapsed,
setInputFocus,
searchInputRef,
setSearchQuery
);
} }
}} }}
type="search" type="search"
@ -188,7 +131,9 @@ const Navbar = ({
</label> </label>
<select <select
name="teacher" name="teacher"
onChange={handleSelectChange} onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="teacherFilter" id="teacherFilter"
> >
<option value="">-</option> <option value="">-</option>
@ -213,7 +158,9 @@ const Navbar = ({
</label> </label>
<select <select
name="type_num" name="type_num"
onChange={handleSelectChange} onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="typeFilter" id="typeFilter"
> >
<option value="">-</option> <option value="">-</option>
@ -228,7 +175,9 @@ const Navbar = ({
</label> </label>
<select <select
name="predmet_type" name="predmet_type"
onChange={handleSelectChange} onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="predmetFilter" id="predmetFilter"
> >
<option value="">-</option> <option value="">-</option>
@ -243,7 +192,9 @@ const Navbar = ({
</label> </label>
<select <select
name="class_num" name="class_num"
onChange={handleSelectChange} onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="classFilter" id="classFilter"
> >
<option value="">-</option> <option value="">-</option>

View File

@ -4,11 +4,11 @@ import './main.css';
import icon from './icon.svg'; import icon from './icon.svg';
import { ILoadingState } from '../types'; import { ILoadingState } from '../types';
const NothingFound = ({ type props = {
setLoading
}: {
setLoading?: Dispatch<SetStateAction<ILoadingState>>; setLoading?: Dispatch<SetStateAction<ILoadingState>>;
}) => { };
const NothingFound: React.FC<props> = ({ setLoading }) => {
useEffect(() => { useEffect(() => {
if (!setLoading) return; if (!setLoading) return;

View File

@ -7,13 +7,12 @@ import Card from '../Card';
import './main.css'; import './main.css';
import NothingFound from '../NothingFound'; import NothingFound from '../NothingFound';
const SubjectList = ({ type props = {
setLoading,
searchQuery
}: {
setLoading: Dispatch<SetStateAction<ILoadingState>>; setLoading: Dispatch<SetStateAction<ILoadingState>>;
searchQuery: IFilterQuery; searchQuery: IFilterQuery;
}) => { };
const SubjectList: React.FC<props> = ({ setLoading, searchQuery }) => {
const [data, setData] = useState<IData[]>([]); const [data, setData] = useState<IData[]>([]);
const { push: historyPush } = useHistory(); const { push: historyPush } = useHistory();