diff --git a/packages/component-faraday-ui/src/AuthorCard.js b/packages/component-faraday-ui/src/AuthorCard.js index 59f4ebe247e23e81dd133c043997c3a0d06ed070..dda6f90471049696813a6b5f88357bf5fb3e7357 100644 --- a/packages/component-faraday-ui/src/AuthorCard.js +++ b/packages/component-faraday-ui/src/AuthorCard.js @@ -5,14 +5,9 @@ import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { required } from 'xpub-validators' import { reduxForm, Field } from 'redux-form' -import { - H3, - Menu, - Spinner, - Checkbox, - TextField, - ValidatedField, -} from '@pubsweet/ui' + +import { MenuCountry } from 'pubsweet-component-faraday-ui' +import { H3, Spinner, Checkbox, TextField, ValidatedField } from '@pubsweet/ui' import { compose, withState, @@ -20,7 +15,6 @@ import { withHandlers, setDisplayName, } from 'recompose' -import { withCountries } from 'pubsweet-component-faraday-ui' import { validators } from './helpers' import { Tag, Label, Row, Item, PersonInfo, IconButton, OpenModal } from './' @@ -196,11 +190,11 @@ const AuthorEdit = ({ <Item vertical> <Label required>Country</Label> <ValidatedField - component={input => ( - <Menu {...input} options={countries} placeholder="Please select" /> - )} + component={MenuCountry} data-test-id="author-card-country" name="country" + placeholder="Please select" + validate={[required]} /> </Item> </Row> @@ -209,7 +203,6 @@ const AuthorEdit = ({ // #endregion const EnhancedAuthorEdit = compose( - withCountries, withProps(({ author }) => ({ initialValues: author, })), diff --git a/packages/component-faraday-ui/src/InviteReviewers.js b/packages/component-faraday-ui/src/InviteReviewers.js index 69b113655a3b73e8e551b2190b7a731de9e414e0..096269be9f94cb15301761fc0be517531858aafe 100644 --- a/packages/component-faraday-ui/src/InviteReviewers.js +++ b/packages/component-faraday-ui/src/InviteReviewers.js @@ -84,10 +84,9 @@ const InviteReviewers = ({ handleSubmit, reset }) => ( <ItemOverrideAlert vertical> <Label required>Country</Label> <ValidatedField - component={input => ( - <MenuCountry {...input} placeholder="Please select" /> - )} + component={MenuCountry} name="country" + placeholder="Please select" validate={[required]} /> </ItemOverrideAlert> @@ -100,7 +99,7 @@ InviteReviewers.propTypes = { * @param {Reviewer} reviewer * @param {object} props */ - onInvite: PropTypes.func, + onInvite: PropTypes.func, // eslint-disable-line } InviteReviewers.defaultProps = { diff --git a/packages/component-faraday-ui/src/MenuCountry.js b/packages/component-faraday-ui/src/MenuCountry.js index 8ca9f618bb2612ebdf04dea4a5c0e781ba2a39ad..60db85789e3f46a9ce3a6a74afb78e7bb66a3c71 100644 --- a/packages/component-faraday-ui/src/MenuCountry.js +++ b/packages/component-faraday-ui/src/MenuCountry.js @@ -1,81 +1,179 @@ -import React from 'react' -import { Menu } from '@pubsweet/ui' -import { startsWith, toLower, get } from 'lodash' -import { compose, withState, withHandlers } from 'recompose' +import React, { Fragment } from 'react' import styled from 'styled-components' -import { th } from '@pubsweet/ui-toolkit' - +import { th, override } from '@pubsweet/ui-toolkit' +import { startsWith, toLower, get, head } from 'lodash' import { withCountries } from 'pubsweet-component-faraday-ui' +import { compose, withState, withHandlers, withProps } from 'recompose' -const filteredCountries = (countries, userInput) => +const filteredCountries = ({ countries, userInput }) => countries.filter(o => startsWith(toLower(o.label), toLower(userInput))) -const firstFilteredCountry = props => - filteredCountries(props.countries, props.userInput)[0] +const firstFilteredCountry = ({ countries, userInput }) => + head(filteredCountries({ countries, userInput })) -const CustomOpener = ({ - selected, +const Menu = ({ + open, + options, + onEnter, userInput, toggleMenu, placeholder, - optionLabel, - onChange, - onEnter, + handleSelect, + onTextChange, }) => ( - <Input - onChange={onChange} - onClick={toggleMenu} - onKeyUp={onEnter} - placeholder={selected ? optionLabel(selected) : placeholder} - value={userInput} - /> -) - -const MenuCountry = ({ countries = [], ...input }) => ( - <Menu - {...input} - options={filteredCountries(countries, input.userInput)} - placeholder="Please select" - renderOpener={CustomOpener} - /> + <Fragment> + {open && <CloseOverlay onClick={toggleMenu} />} + <Main> + <Input + onChange={onTextChange} + onClick={toggleMenu} + onKeyUp={onEnter} + placeholder={placeholder} + value={userInput} + /> + {open && ( + <Options> + {options.map(option => ( + <Option key={option.value} onClick={handleSelect(option.value)}> + {option.label} + </Option> + ))} + </Options> + )} + </Main> + </Fragment> ) -const enhance = compose( +export default compose( withCountries, withState('userInput', 'updateUserInput', ''), + withState('open', 'updateOptionsVisibility', false), withHandlers({ - onChange: ({ updateUserInput, onChange }) => value => { - // this value is an input DOM event while typing and a dropdown value when - // selected - if (typeof value === 'string') { + handleSelect: ({ + onChange, + countryLabel, + updateUserInput, + updateOptionsVisibility, + }) => value => () => { + const country = countryLabel(value) + if (country) { onChange(value) + updateUserInput(country) + updateOptionsVisibility(false) } - updateUserInput(get(value, 'target.value', '')) }, - onEnter: props => event => { + }), + withHandlers({ + toggleMenu: ({ updateOptionsVisibility, open }) => () => { + updateOptionsVisibility(!open) + }, + onTextChange: ({ updateUserInput, countryLabel, onChange }) => event => { + const inputValue = get(event, 'target.value', '') + const country = countryLabel(inputValue) + + updateUserInput(inputValue) + if (!country) { + onChange('') + } + }, + onEnter: ({ handleSelect, userInput, countries }) => event => { if (event.which === 13) { - props.onChange(firstFilteredCountry(props).value) - props.updateUserInput(firstFilteredCountry(props).label) + handleSelect( + get(firstFilteredCountry({ countries, userInput }), 'value'), + )() } }, }), -) - -export default enhance(MenuCountry) + withProps(({ countries, userInput }) => ({ + options: filteredCountries({ countries, userInput }), + })), +)(Menu) +// #region styles const Input = styled.input` width: 100%; height: calc(${th('gridUnit')} * 4); border: ${th('accordion.border')}; border-radius: ${th('borderRadius')}; padding: 0 ${th('gridUnit')}; + font-family: ${th('fontHeading')}; ::placeholder { color: ${th('colorText')}; opacity: 1; font-family: ${th('fontWriting')}; + font-style: italic; } :focus { border-color: ${th('action.colorActive')} outline: none; } ` + +const CloseOverlay = styled.div` + background-color: transparent; + position: fixed; + bottom: 0; + left: 0; + top: 0; + right: 0; + z-index: 10; + + ${override('ui.MenuCountry.CloseOverlay')}; +` + +const Main = styled.div.attrs(props => ({ + role: 'listbox', +}))` + position: relative; + + ${override('ui.MenuCountry.Main')}; +` + +const Options = styled.div` + position: absolute; + top: 35px; + left: 0; + right: 0; + + background-color: ${th('colorBackground')}; + border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + border-radius: ${th('borderRadius')}; + overflow-y: auto; + max-height: ${({ maxHeight }) => `${maxHeight || 250}px`}; + max-width: ${({ maxWidth }) => `${maxWidth || 200}px`}; + z-index: 100; + + ${override('ui.MenuCountry.Options')}; +` + +const Option = styled.div.attrs(props => ({ + role: 'option', + tabIndex: '0', + 'aria-selected': props.active, +}))` + color: ${props => (props.active ? props.theme.textColor : '#444')}; + font-weight: ${props => (props.active ? '600' : 'inherit')}; + cursor: pointer; + font-family: ${th('fontAuthor')}; + padding: calc(${th('gridUnit')} - ${th('borderWidth')} * 2) + calc(${th('gridUnit')} * 2); + border: ${th('borderWidth')} ${th('borderStyle')} transparent; + border-width: ${th('borderWidth')} 0 ${th('borderWidth')} 0; + white-space: nowrap; + + &:hover { + background: ${th('colorBackgroundHue')}; + border-color: ${th('colorBorder')}; + } + + &:first-child:hover { + border-top-color: ${th('colorBackgroundHue')}; + } + + &:last-child:hover { + border-bottom-color: ${th('colorBackgroundHue')}; + } + + ${override('ui.MenuCountry.Option')}; +` +// #endregion diff --git a/packages/component-faraday-ui/src/Pagination.js b/packages/component-faraday-ui/src/Pagination.js index d2a7701279a675b0b890f5b87f4b48270546084a..4220480d63dbc0390f85434753d617a600f7467b 100644 --- a/packages/component-faraday-ui/src/Pagination.js +++ b/packages/component-faraday-ui/src/Pagination.js @@ -28,8 +28,8 @@ const PaginationComponent = ({ iconSize={2} onClick={toFirst} pb={0.5} - pr={2} pl={1} + pr={2} /> <Chevrons className="caratRight" diff --git a/packages/component-faraday-ui/src/UserProfile.js b/packages/component-faraday-ui/src/UserProfile.js index 7c59dda1bef5d1d2c9f17c011966e9ca87666ca1..2cbe0e628a84bf32a9911abaa58a3c748d68e151 100644 --- a/packages/component-faraday-ui/src/UserProfile.js +++ b/packages/component-faraday-ui/src/UserProfile.js @@ -5,7 +5,7 @@ import styled from 'styled-components' import { reduxForm } from 'redux-form' import React, { Fragment } from 'react' import { th } from '@pubsweet/ui-toolkit' -import { withCountries } from 'pubsweet-component-faraday-ui' +import { withCountries, MenuCountry } from 'pubsweet-component-faraday-ui' import { required as requiredValidator } from 'xpub-validators' import { compose, withStateHandlers, withProps } from 'recompose' import { H3, Spinner, ValidatedField, TextField, Menu } from '@pubsweet/ui' @@ -182,8 +182,9 @@ const EditUserProfile = compose( <Item ml={1} vertical> <Label required>Country</Label> <ValidatedField - component={input => <Menu {...input} options={countries} />} + component={MenuCountry} name="country" + placeholder="Please select" validate={[requiredValidator]} /> </Item> diff --git a/packages/component-faraday-ui/src/helpers/withCountries.js b/packages/component-faraday-ui/src/helpers/withCountries.js index fb72d5997d1270d94f7a11dd8325510a24034e1e..74748e41c8e0a5889d5b85f5aa5f57a122852164 100644 --- a/packages/component-faraday-ui/src/helpers/withCountries.js +++ b/packages/component-faraday-ui/src/helpers/withCountries.js @@ -14,7 +14,7 @@ const countryMapper = (c = 'GB') => { } } -const codeMapper = (c = 'UK') => { +const codeMapper = (c = '') => { switch (c) { case 'UK': return 'GB' diff --git a/packages/components-faraday/src/components/SignUp/SignUpStep0.js b/packages/components-faraday/src/components/SignUp/SignUpStep0.js index e030a1a9c9f420639f2c189ba95835f277a5f3fa..804ce99d10b48e9a88d1024f3cf085371b450be9 100644 --- a/packages/components-faraday/src/components/SignUp/SignUpStep0.js +++ b/packages/components-faraday/src/components/SignUp/SignUpStep0.js @@ -67,10 +67,9 @@ const Step0 = ({ type, error, journal, handleSubmit, initialValues }) => <ItemOverrideAlert data-test-id="sign-up-country" ml={1} vertical> <Label required>Country</Label> <ValidatedField - component={input => ( - <MenuCountry {...input} placeholder="Please select" /> - )} + component={MenuCountry} name="country" + placeholder="Please select" validate={[requiredValidator]} /> </ItemOverrideAlert> diff --git a/packages/hindawi-theme/src/index.js b/packages/hindawi-theme/src/index.js index fa72ae39d8eb011a3f3f752d89f6c74295f1558d..2054d8837eaba732ea085609e8ad95c4c2b0655b 100644 --- a/packages/hindawi-theme/src/index.js +++ b/packages/hindawi-theme/src/index.js @@ -174,6 +174,7 @@ const hindawiTheme = { { Icon, Menu, + MenuCountry: Menu, Radio, Steps, Action,