diff --git a/packages/component-faraday-ui/src/MenuCountry.js b/packages/component-faraday-ui/src/MenuCountry.js index 8ca9f618bb2612ebdf04dea4a5c0e781ba2a39ad..910339197883a3cf0a11073f0abaa76903635ed5 100644 --- a/packages/component-faraday-ui/src/MenuCountry.js +++ b/packages/component-faraday-ui/src/MenuCountry.js @@ -1,9 +1,8 @@ import React from 'react' -import { Menu } from '@pubsweet/ui' import { startsWith, toLower, get } from 'lodash' import { compose, withState, withHandlers } from 'recompose' import styled from 'styled-components' -import { th } from '@pubsweet/ui-toolkit' +import { th, override } from '@pubsweet/ui-toolkit' import { withCountries } from 'pubsweet-component-faraday-ui' @@ -13,51 +12,119 @@ const filteredCountries = (countries, userInput) => const firstFilteredCountry = props => filteredCountries(props.countries, props.userInput)[0] -const CustomOpener = ({ +const Opener = ({ selected, userInput, toggleMenu, placeholder, optionLabel, + removeSelectedOption, onChange, onEnter, }) => ( <Input onChange={onChange} - onClick={toggleMenu} + onClick={selected ? removeSelectedOption : toggleMenu} onKeyUp={onEnter} - placeholder={selected ? optionLabel(selected) : placeholder} - value={userInput} + placeholder={placeholder} + value={selected ? optionLabel(selected) : userInput} /> ) const MenuCountry = ({ countries = [], ...input }) => ( <Menu + maxHeight={250} + maxWidth={200} {...input} options={filteredCountries(countries, input.userInput)} placeholder="Please select" - renderOpener={CustomOpener} /> ) +const Menu = props => ( + <Root> + {props.label && <Label onClick={props.optionLabel}>{props.label}</Label>} + {props.open && <CloseOverlay onClick={props.toggleMenu} />} + <Main> + <Opener + open={props.open} + optionLabel={props.optionLabel} + placeholder={props.placeHolder} + removeSelectedOption={props.removeSelectedOption} + selected={props.selected} + toggleMenu={props.toggleMenu} + {...props} + /> + <OptionsContainer> + {props.open && ( + <Options maxHeight={props.maxHeight} open={props.open}> + {props.options.map(option => ( + <MenuOption + handleSelect={props.handleSelect} + key={option.value} + label={option.label} + selected={props.selected} + value={option.value} + /> + ))} + </Options> + )} + </OptionsContainer> + </Main> + </Root> +) + +const MenuOption = ({ label, selected, value, handleSelect }) => ( + <Option + active={value === selected} + key={value} + onClick={() => { + handleSelect(value) + }} + > + {label || value} + </Option> +) + const enhance = compose( withCountries, withState('userInput', 'updateUserInput', ''), + withState('open', 'updateOptionsVizibility', false), + withState('selected', 'setSelectedValue', ''), withHandlers({ - onChange: ({ updateUserInput, onChange }) => value => { - // this value is an input DOM event while typing and a dropdown value when - // selected - if (typeof value === 'string') { - onChange(value) - } + optionLabel: ({ countries }) => value => { + const label = countries.find(country => country.value === value) + ? countries.find(country => country.value === value).label + : '' + return label + }, + handleSelect: ({ setSelectedValue, updateOptionsVizibility }) => value => { + setSelectedValue(value) + updateOptionsVizibility(false) + }, + }), + withHandlers({ + onChange: ({ updateUserInput }) => value => { updateUserInput(get(value, 'target.value', '')) }, onEnter: props => event => { if (event.which === 13) { - props.onChange(firstFilteredCountry(props).value) + props.handleSelect(firstFilteredCountry(props).value) props.updateUserInput(firstFilteredCountry(props).label) } }, + toggleMenu: ({ updateOptionsVizibility, open }) => event => { + updateOptionsVizibility(!open) + }, + removeSelectedOption: ({ + setSelectedValue, + updateOptionsVizibility, + updateUserInput, + }) => event => { + setSelectedValue('') + updateUserInput('') + updateOptionsVizibility(true) + }, }), ) @@ -69,13 +136,101 @@ const Input = styled.input` 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 Root = styled.div`` + +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 Label = styled.label` + font-size: ${th('fontSizeBaseSmall')}; + line-height: ${th('lineHeightBaseSmall')}; + display: block; + + ${override('ui.Label')}; + ${override('ui.MenuCountry.Label')}; +` + +const Main = styled.div.attrs(props => ({ + role: 'listbox', +}))` + position: relative; + + ${override('ui.MenuCountry.Main')}; +` + +const OptionsContainer = styled.div` + position: absolute; + left: 0; + right: 0; + + ${override('ui.MenuCountry.OptionsContainer')}; +` + +const Options = styled.div` + position: absolute; + top: 0; + 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}px`}; + max-width: ${({ maxWidth }) => `${maxWidth}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')}; +` 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,