feat(ott-250): added preloader (#149)

* feat(ott-250): added preloader

* fix(ott-250): deleted white space

* fix(ott-250): not finshied yet

* fix(ott-250): added useRequest hooks

* fix(ott-250): minor bug

Co-authored-by: Armen <armen9339@gmail.com>
keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Armen9339 5 years ago committed by GitHub
parent 194d09e009
commit df16579eb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/features/Common/Input/index.tsx
  2. 16
      src/features/Loader/index.tsx
  3. 57
      src/features/Loader/styled.tsx
  4. 6
      src/features/Loader/types.tsx
  5. 15
      src/features/Search/hooks/index.tsx
  6. 10
      src/features/Search/index.tsx
  7. 36
      src/features/Search/styled.tsx

@ -14,6 +14,7 @@ import {
} from './styled' } from './styled'
type Props = { type Props = {
autoComplete?: string,
defaultValue?: string, defaultValue?: string,
id: string, id: string,
inputWidth?: number, inputWidth?: number,
@ -32,6 +33,7 @@ type Props = {
} & WrapperProps } & WrapperProps
export const Input = ({ export const Input = ({
autoComplete = '',
defaultValue, defaultValue,
error, error,
id, id,
@ -70,6 +72,7 @@ export const Input = ({
} }
</Label> </Label>
<InputStyled <InputStyled
autoComplete={autoComplete}
id={id} id={id}
type={type} type={type}
required={required} required={required}

@ -0,0 +1,16 @@
import React from 'react'
import { LoaderProps } from './types'
import { Wrapper, Circles } from './styled'
export const Loader = ({ color, diameter }: LoaderProps) => (
<Wrapper color={color} diameter={diameter}>
<Circles color={color} />
</Wrapper>
)
export const LoaderUI = {
Circles,
Loader,
Wrapper,
}

@ -0,0 +1,57 @@
import styled, { css, keyframes } from 'styled-components'
import { LoaderProps } from './types'
const animation = keyframes`
0%,
100% {
transform: scale(0);
}
50% {
transform: scale(1.0);
}
`
export const Wrapper = styled.div<LoaderProps>`
position: relative;
margin: 0 auto;
${({ diameter = 25 }) => (
diameter
? css`
width: ${diameter}px;
height: ${diameter}px;
`
: ''
)}
:before,
:after {
content: '';
position: absolute;
top: 0;
background-color: ${({ color }) => color};
width: 100%;
height: 100%;
border-radius: 50%;
animation: ${animation} 1s infinite ease-in-out;
}
:before {
left: -130%;
animation-delay: 0.2s;
}
:after {
left: 130%;
animation-delay: -0.2s;
}
`
export const Circles = styled.div<LoaderProps>`
background-color: ${({ color }) => color};
width: 100%;
height: 100%;
border-radius: 50%;
animation: ${animation} 1s infinite ease-in-out;
`

@ -0,0 +1,6 @@
export type LoaderProps = {
/** цвет кружков прелоадера */
color?: string,
/** диаметр кружков прелоадера */
diameter?: number,
}

@ -13,7 +13,10 @@ import size from 'lodash/size'
import type { SearchItems } from 'requests' import type { SearchItems } from 'requests'
import { getSearchItems } from 'requests' import { getSearchItems } from 'requests'
import { useToggle } from 'hooks' import {
useRequest,
useToggle,
} from 'hooks'
import { SEARCH_DELAY, MIN_CHARACTERS_LENGTH } from '../config' import { SEARCH_DELAY, MIN_CHARACTERS_LENGTH } from '../config'
import { useNormalizedItems } from './useNormalizedItems' import { useNormalizedItems } from './useNormalizedItems'
@ -21,17 +24,22 @@ import { useNormalizedItems } from './useNormalizedItems'
export const useSearch = () => { export const useSearch = () => {
const [searchItems, setSearchItems] = useState<SearchItems>({}) const [searchItems, setSearchItems] = useState<SearchItems>({})
const abortControllerRef = useRef<AbortController | null>(null) const abortControllerRef = useRef<AbortController | null>(null)
const { const {
close, close,
isOpen, isOpen,
open, open,
} = useToggle() } = useToggle()
const {
isFetching,
request: searchItemsRequest,
} = useRequest(getSearchItems)
const fetchSearchItems = useCallback(debounce((searchString: string) => { const fetchSearchItems = useCallback(debounce((searchString: string) => {
const abortController = new window.AbortController() const abortController = new window.AbortController()
abortControllerRef.current = abortController abortControllerRef.current = abortController
searchItemsRequest(searchString, abortController.signal).then((data) => {
getSearchItems(searchString, abortController.signal).then((data) => {
setSearchItems(data) setSearchItems(data)
abortControllerRef.current = null abortControllerRef.current = null
}) })
@ -76,6 +84,7 @@ export const useSearch = () => {
return { return {
close, close,
isFetching,
normalizedItems: useNormalizedItems(searchItems), normalizedItems: useNormalizedItems(searchItems),
onChange, onChange,
onFocus, onFocus,

@ -6,6 +6,7 @@ import { PAGES } from 'config'
import { Input } from 'features/Common' import { Input } from 'features/Common'
import { ItemsList } from 'features/ItemsList' import { ItemsList } from 'features/ItemsList'
import { OutsideClick } from 'features/OutsideClick' import { OutsideClick } from 'features/OutsideClick'
import { Loader } from 'features/Loader'
import { useSearch } from './hooks' import { useSearch } from './hooks'
import { Header } from './components/Header' import { Header } from './components/Header'
@ -13,11 +14,13 @@ import {
Wrapper, Wrapper,
Form, Form,
Results, Results,
LoaderWrapper,
} from './styled' } from './styled'
export const Search = () => { export const Search = () => {
const { const {
close, close,
isFetching,
normalizedItems: { normalizedItems: {
players, players,
teams, teams,
@ -28,17 +31,24 @@ export const Search = () => {
onSubmit, onSubmit,
showResults, showResults,
} = useSearch() } = useSearch()
const isMatch = useRouteMatch(`/:sportName${PAGES.match}/:pageId`)?.isExact || false const isMatch = useRouteMatch(`/:sportName${PAGES.match}/:pageId`)?.isExact || false
return ( return (
<OutsideClick onClick={close}> <OutsideClick onClick={close}>
<Wrapper> <Wrapper>
<Form role='search' onSubmit={onSubmit} isMatch={isMatch}> <Form role='search' onSubmit={onSubmit} isMatch={isMatch}>
<Input <Input
autoComplete='off'
id='searchInput' id='searchInput'
type='search' type='search'
onChange={onChange} onChange={onChange}
onFocus={onFocus} onFocus={onFocus}
/> />
{isFetching && (
<LoaderWrapper>
<Loader color='#515151' />
</LoaderWrapper>
)}
</Form> </Form>
{showResults && ( {showResults && (
<Results> <Results>

@ -11,12 +11,34 @@ import {
export const Wrapper = styled.div` export const Wrapper = styled.div`
position: relative; position: relative;
`
export const LoaderWrapper = styled.div`
position: absolute;
top: 0;
background-color: rgba(129, 129, 129, 0.5);
width: 100%;
height: 48px;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3);
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
@media ${devices.mobile} {
top: 20px;
} }
@media ${devices.tablet} {
top: 26px;
height: 28px;
}
` `
export const Form = styled.form<{isMatch: boolean}>` export const Form = styled.form<{isMatch: boolean}>`
width: 288px; width: 288px;
position: relative;
${InputWrapper} { ${InputWrapper} {
margin: 0; margin: 0;
padding-bottom: 13px; padding-bottom: 13px;
@ -31,6 +53,10 @@ export const Form = styled.form<{isMatch: boolean}>`
margin-top: 25px; margin-top: 25px;
background-color: transparent; background-color: transparent;
} }
@media ${devices.mobile} {
margin-top: 20px;
}
} }
@media ${devices.desktop} { @media ${devices.desktop} {
@ -52,6 +78,12 @@ export const Form = styled.form<{isMatch: boolean}>`
} }
} }
@media ${devices.mobile} {
:focus-within {
top: -33px;
}
}
${InputStyled} { ${InputStyled} {
width: 100%; width: 100%;
@ -84,7 +116,7 @@ export const Form = styled.form<{isMatch: boolean}>`
margin-right: 0; margin-right: 0;
} }
@media ${devices.tablet} { @media ${devices.tablet} {
margin-bottom: 2px; margin-right: 0;
background-image: url(/images/search-mob.svg); background-image: url(/images/search-mob.svg);
} }
} }

Loading…
Cancel
Save