diff --git a/package.json b/package.json index a0e041a5..fd673ac5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build-storybook": "build-storybook -s public" }, "dependencies": { + "@reach/combobox": "^0.10.4", "history": "^4.10.1", "lodash": "^4.17.15", "react": "^16.13.1", diff --git a/public/images/arrowDown.svg b/public/images/arrowDown.svg new file mode 100644 index 00000000..94f5058a --- /dev/null +++ b/public/images/arrowDown.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/features/Common/Input/styled.tsx b/src/features/Common/Input/styled.tsx index 708cd621..873080ba 100644 --- a/src/features/Common/Input/styled.tsx +++ b/src/features/Common/Input/styled.tsx @@ -1,11 +1,11 @@ -import styled from 'styled-components/macro' +import styled, { css } from 'styled-components/macro' export type TInputWrapper = { paddingX?: number, wrapperWidth?: number, } -export const InputWrapper = styled.div` +export const wrapperStyles = css` width: ${({ wrapperWidth }) => (wrapperWidth ? `${wrapperWidth}px` : '100%')}; height: 48px; margin: 20px 0; @@ -20,6 +20,10 @@ export const InputWrapper = styled.div` border-radius: 2px; ` +export const InputWrapper = styled.div` + ${wrapperStyles} +` + type TLabel = { labelWidth?: number, } @@ -39,7 +43,7 @@ type TInputStyled = { inputWidth?: number, } -export const InputStyled = styled.input` +export const inputStyles = css` flex-grow: 1; font-weight: bold; font-size: 20px; @@ -68,3 +72,7 @@ export const InputStyled = styled.input` -webkit-text-fill-color: ${({ theme: { colors } }) => colors.text}; } ` + +export const InputStyled = styled.input` + ${inputStyles} +` diff --git a/src/features/CountrySelector/hooks.tsx b/src/features/CountrySelector/hooks.tsx new file mode 100644 index 00000000..61a69061 --- /dev/null +++ b/src/features/CountrySelector/hooks.tsx @@ -0,0 +1,88 @@ +import type { ChangeEvent, FocusEvent } from 'react' +import type { Countries } from 'requests' + +import { + useEffect, + useState, + useCallback, +} from 'react' + +import toLower from 'lodash/toLower' +import slice from 'lodash/slice' +import find from 'lodash/find' + +import { getCountries } from 'requests' + +import { matchSort } from './helpers' + +// временно, будем считывать из стора(контекста) лексики +const useCurrentLang = () => 'eng' + +const useCountriesList = () => { + const [countries, setCountries] = useState([]) + + useEffect(() => { + getCountries().then(setCountries) + }, []) + + return countries +} + +const useQuery = () => { + const [query, setQuery] = useState('') + + const onQueryChange = useCallback(({ target }: ChangeEvent) => { + setQuery(target.value) + }, []) + + return { + onQueryChange, + query, + setQuery, + } +} + +const isOptionClicked = (target: HTMLElement) => target?.getAttribute('role') === 'option' + +export const useCountrySelector = () => { + const lang = useCurrentLang() + const countries = useCountriesList() + const { + onQueryChange, + query, + setQuery, + } = useQuery() + + const keyToSortBy = `name_${lang}` as 'name_eng' + + const results = matchSort( + countries, + keyToSortBy, + query, + ) + + const onBlur = (event: FocusEvent) => { + const target = event.relatedTarget as HTMLElement | null + // клик по элементу списка тоже вызывает onBlur + // если кликали элемент списка то событие обрабатывается onCountrySelect + if (target && isOptionClicked(target)) return + + const found = find( + countries, + (country) => toLower(country[keyToSortBy]) === toLower(query), + ) + setQuery(found ? found[keyToSortBy] : '') + } + + return { + countries: slice( + results, + 0, + 20, + ), + onBlur, + onCountrySelect: setQuery, + onQueryChange, + query, + } +} diff --git a/src/features/CountrySelector/index.tsx b/src/features/CountrySelector/index.tsx new file mode 100644 index 00000000..07912710 --- /dev/null +++ b/src/features/CountrySelector/index.tsx @@ -0,0 +1,63 @@ +import React from 'react' + +import isEmpty from 'lodash/isEmpty' +import map from 'lodash/map' +import { + ComboboxPopover, + ComboboxList, + ComboboxOption, +} from '@reach/combobox' +import '@reach/combobox/styles.css' + +import { Label } from 'features/Common/Input/styled' + +import { useCountrySelector } from './hooks' +import { + ComboboxStyled, + ComboboxInputStyled, + Arrow, +} from './styled' + +type Props = { + labelWidth?: number, +} + +export const CountrySelector = ({ labelWidth }: Props) => { + const { + countries, + onBlur, + onCountrySelect, + onQueryChange, + query, + } = useCountrySelector() + + return ( + + + + {!isEmpty(countries) && ( + + + {map(countries, ({ id, name_eng }) => ( + + ))} + + + )} + + ) +} diff --git a/src/features/CountrySelector/styled.tsx b/src/features/CountrySelector/styled.tsx new file mode 100644 index 00000000..bcd76ac8 --- /dev/null +++ b/src/features/CountrySelector/styled.tsx @@ -0,0 +1,27 @@ +import styled from 'styled-components/macro' + +import { Combobox, ComboboxInput } from '@reach/combobox' + +import { wrapperStyles, inputStyles } from 'features/Common/Input/styled' + +export const ComboboxStyled = styled(Combobox)` + ${wrapperStyles} + position: relative; +` + +export const ComboboxInputStyled = styled(ComboboxInput)` + ${inputStyles} + padding-right: 24px; +` + +export const Arrow = styled.div` + position: absolute; + right: 20px; + top: 50%; + transform: translateY(-50%); + width: 12px; + height: 12px; + background-image: url(/images/arrowDown.svg); + background-position: center; + background-repeat: no-repeat; +` diff --git a/src/features/Register/Steps/Registration/index.tsx b/src/features/Register/Steps/Registration/index.tsx index 9e5a5fe8..3eefccde 100644 --- a/src/features/Register/Steps/Registration/index.tsx +++ b/src/features/Register/Steps/Registration/index.tsx @@ -2,6 +2,7 @@ import React from 'react' import { PAGES } from 'config' +import { CountrySelector } from 'features/CountrySelector' import { Input } from 'features/Common' import { BlockTitle, @@ -38,15 +39,15 @@ export const Registration = () => ( label='E-mail' labelWidth={labelWidth} /> - {/* TODO: it should be Dropdown */} +