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';
const Card = ({ data }: { data: IData }) => (
type props = {
data: IData;
};
const Card: React.FC<props> = ({ data }) => (
<a
className="card"
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 { motion, Transition } from 'framer-motion';
import { useHistory } from 'react-router-dom';
import React, { Dispatch, SetStateAction } from 'react';
import { motion } from 'framer-motion';
import { useLocation } from 'react-router-dom';
import { IFilterQuery, ILoadingState } from '../types';
import './main.css';
import Logotype from '../Logotype';
import { genName } from './utils';
import {
headerTransition,
headerVariants,
loadingLogoTransition
} from './animations';
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 '';
};
const Header = ({
query,
loading,
setSearchQuery
}: {
type props = {
query: IFilterQuery;
loading: ILoadingState;
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
}) => {
};
const Header: React.FC<props> = ({ query, loading, setSearchQuery }) => {
/*
* Hooks definitions
*/
const history = useHistory();
useEffect(() => {}, [loading]);
/*
* Animations
*/
const headerVariants = {
expanded: {
height: '100vh'
},
collapsed: {
height: '10vh'
}
};
const transition: Transition = {
duration: 0.5,
ease: 'linear'
};
const { pathname } = useLocation();
return (
<motion.header
id="name"
variants={headerVariants}
transition={transition}
transition={headerTransition}
animate={loading.fetching ? 'expanded' : 'collapsed'}
initial="expanded"
>
<h1>{genName(query, history.location.pathname, loading.error)}</h1>
<h1>{genName(query, pathname, loading.error)}</h1>
{loading.fetching ? (
<motion.div
id="loadingLogo"
animate={{ rotate: 360 }}
transition={{ loop: Infinity, ease: 'linear', duration: 3 }}
transition={loadingLogoTransition}
>
<Logotype setSearchQuery={setSearchQuery} />
</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 NothingFound from '../NothingFound';
import { IData, IFilterQuery, ILoadingState } from '../types';
import { handleShowMore } from './handlers';
import './main.css';
const Home = ({
setLoading,
setSearchQuery
}: {
type props = {
setLoading: Dispatch<SetStateAction<ILoadingState>>;
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
}) => {
};
const Home: React.FC<props> = ({ setLoading, setSearchQuery }) => {
const [data, setData] = useState<IData[]>([]);
useEffect(() => {
@ -38,18 +38,6 @@ const Home = ({
...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 (
<main className="homeContainer">
{classes.length ? (
@ -87,7 +75,8 @@ const Home = ({
onClick={() =>
handleShowMore(
subject,
class_num
class_num,
setSearchQuery
)
}
to={'/list'}

View File

@ -4,13 +4,13 @@ import { Link } from 'react-router-dom';
import { emptyQuery } from '../Navbar/utils';
import { IFilterQuery } from '../types';
import LogoImage from './logo.png';
import './main.css'
import './main.css';
const Logotype = ({
setSearchQuery
}: {
type props = {
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
}) => {
};
const Logotype: React.FC<props> = ({ setSearchQuery }) => {
return (
<Link
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, {
ChangeEvent,
Dispatch,
SetStateAction,
useEffect,
@ -14,25 +13,31 @@ import SearchIcon from './search.svg';
import { IFilterQuery } from '../types';
import { useFocus } from '../utils';
import Logotype from '../Logotype';
import {
filtersVariants,
navVariants,
searchVariants,
transition
} from './animations';
import {
handleFiltersButton,
handleSearchButton,
handleSelectChange
} from './handlers';
const Navbar = ({
setSearchQuery,
query
}: {
type props = {
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
query: IFilterQuery;
}) => {
/*
* Hooks
*/
};
const Navbar: React.FC<props> = ({ setSearchQuery, query }) => {
const [searchCollapsed, setSearchCollapsed] = 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);
@ -48,89 +53,6 @@ const Navbar = ({
}
}, [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 (
<motion.header
id="navbar"
@ -146,7 +68,14 @@ const Navbar = ({
<button
className="navButton"
id="filter"
onClick={handleFiltersButton}
onClick={() =>
handleFiltersButton(
filtersCollapsed,
localFilters,
setSearchQuery,
setFiltersCollapsed
)
}
>
<img src={FilterIcon} alt="Фильтр" />
</button>
@ -154,7 +83,15 @@ const Navbar = ({
<button
className="navButton"
id="search"
onClick={handleSearchButton}
onClick={() =>
handleSearchButton(
searchCollapsed,
setSearchCollapsed,
setInputFocus,
searchInputRef,
setSearchQuery
)
}
>
<img src={SearchIcon} alt="Поиск" />
</button>
@ -163,10 +100,16 @@ const Navbar = ({
variants={searchVariants}
transition={transition}
aria-label="Поиск"
ref={searchInput}
ref={searchInputRef}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSearchButton();
handleSearchButton(
searchCollapsed,
setSearchCollapsed,
setInputFocus,
searchInputRef,
setSearchQuery
);
}
}}
type="search"
@ -188,7 +131,9 @@ const Navbar = ({
</label>
<select
name="teacher"
onChange={handleSelectChange}
onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="teacherFilter"
>
<option value="">-</option>
@ -213,7 +158,9 @@ const Navbar = ({
</label>
<select
name="type_num"
onChange={handleSelectChange}
onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="typeFilter"
>
<option value="">-</option>
@ -228,7 +175,9 @@ const Navbar = ({
</label>
<select
name="predmet_type"
onChange={handleSelectChange}
onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="predmetFilter"
>
<option value="">-</option>
@ -243,7 +192,9 @@ const Navbar = ({
</label>
<select
name="class_num"
onChange={handleSelectChange}
onChange={(e) =>
handleSelectChange(e.target, setLocalFilters)
}
id="classFilter"
>
<option value="">-</option>

View File

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

View File

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