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. 54
      src/features/Search/styled.tsx

@ -14,6 +14,7 @@ import {
} from './styled'
type Props = {
autoComplete?: string,
defaultValue?: string,
id: string,
inputWidth?: number,
@ -32,6 +33,7 @@ type Props = {
} & WrapperProps
export const Input = ({
autoComplete = '',
defaultValue,
error,
id,
@ -70,6 +72,7 @@ export const Input = ({
}
</Label>
<InputStyled
autoComplete={autoComplete}
id={id}
type={type}
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 { getSearchItems } from 'requests'
import { useToggle } from 'hooks'
import {
useRequest,
useToggle,
} from 'hooks'
import { SEARCH_DELAY, MIN_CHARACTERS_LENGTH } from '../config'
import { useNormalizedItems } from './useNormalizedItems'
@ -21,17 +24,22 @@ import { useNormalizedItems } from './useNormalizedItems'
export const useSearch = () => {
const [searchItems, setSearchItems] = useState<SearchItems>({})
const abortControllerRef = useRef<AbortController | null>(null)
const {
close,
isOpen,
open,
} = useToggle()
const {
isFetching,
request: searchItemsRequest,
} = useRequest(getSearchItems)
const fetchSearchItems = useCallback(debounce((searchString: string) => {
const abortController = new window.AbortController()
abortControllerRef.current = abortController
getSearchItems(searchString, abortController.signal).then((data) => {
searchItemsRequest(searchString, abortController.signal).then((data) => {
setSearchItems(data)
abortControllerRef.current = null
})
@ -76,6 +84,7 @@ export const useSearch = () => {
return {
close,
isFetching,
normalizedItems: useNormalizedItems(searchItems),
onChange,
onFocus,

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

@ -11,26 +11,52 @@ import {
export const Wrapper = styled.div`
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}>`
width: 288px;
position: relative;
${InputWrapper} {
margin: 0;
padding-bottom: 13px;
@media ${devices.desktop} {
padding-left: 12px;
}
padding-left: 12px;
}
@media ${devices.tablet} {
padding-left: 6px;
height: 12px;
margin-top: 25px;
background-color: transparent;
}
@media ${devices.tablet} {
padding-left: 6px;
height: 12px;
margin-top: 25px;
background-color: transparent;
}
@media ${devices.mobile} {
margin-top: 20px;
}
}
@media ${devices.desktop} {
@ -51,6 +77,12 @@ export const Form = styled.form<{isMatch: boolean}>`
left: -240px;
}
}
@media ${devices.mobile} {
:focus-within {
top: -33px;
}
}
${InputStyled} {
width: 100%;
@ -84,7 +116,7 @@ export const Form = styled.form<{isMatch: boolean}>`
margin-right: 0;
}
@media ${devices.tablet} {
margin-bottom: 2px;
margin-right: 0;
background-image: url(/images/search-mob.svg);
}
}

Loading…
Cancel
Save