Skip to content
Snippets Groups Projects
Menu.js 4.28 KiB
Newer Older
import React from 'react'
import styled from 'styled-components'
// TODO: match the width of the container to the width of the widest option?
Alf Eaton's avatar
Alf Eaton committed
// TODO: use a <select> element instead of divs?
const Root = styled.div`
  font-size: 1em;
`

const Label = styled.span`
  display: block;
  font-size: 1em;
  margin-bottom: 0.5em;
`

const OpenerContainer = styled.div``

const Opener = styled.button.attrs({
  type: 'button',
})`
  background: transparent;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 1.2em;

  border-left: 2px solid
    ${props => (props.open ? 'var(--color-primary)' : 'lightgrey')};
  color: ${props => (props.open ? 'var(--color-primary)' : 'inherit')};

  &:hover {
    border-left: 2px solid var(--color-primary);
    color: var(--color-primary);
  }
`

const Placeholder = styled.span`
  font-family: var(--font-interface);
  font-style: italic;
  font-weight: 400;
  text-transform: normal;
  color: #aaa;

  &:hover {
    color: var(--color-primary);
  }
`

const Arrow = styled.span`
  display: inline-block;
  font-size: 50%;
  margin-left: 10px;
  transition: transform 0.2s;
  transform: scaleX(2.2) scaleY(${props => (props.open ? -1.2 : 1.2)});
`

const Main = styled.div.attrs({
  role: 'listbox',
})`
  position: relative;
`

const OptionsContainer = styled.div`
  position: absolute;
`

const Options = styled.div`
  background-color: white;
  border-bottom: 2px solid var(--color-primary);
  border-left: 2px solid var(--color-primary);
  left: 0;
  padding-bottom: 0.5em;
  padding-top: 0.5em;
  position: absolute;
  top: 0;
  transition: opacity 2s;
  width: 0;
  z-index: 10;

  min-width: ${props => (props.open ? '10em' : '0')};
  opacity: ${props => (props.open ? '1' : '0')};
`

const Option = styled.div.attrs({
  role: 'option',
  tabIndex: '0',
  'aria-selected': props => props.active,
})`
  color: ${props => (props.active ? 'black' : '#444')};
  font-weight: ${props => (props.active ? '600' : 'inherit')};
  cursor: pointer;
  font-family: var(--font-author);
  padding: 10px;
  white-space: nowrap;

  &:hover {
    color: var(--color-primary);
  }
`

/* Not used for now
.inline {
  align-items: flex-end;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  margin-right: 0.5em;
}

.inline .label {
  margin-right: 0.5em;
}

.inline .opener {
  margin-bottom: -4px;
}

.root .inline {
  flex-direction: columns;
}

.root.author {
  font-family: var(--font-author);
}
*/

class Menu extends React.Component {
Yannis Barlas's avatar
Yannis Barlas committed
  constructor(props) {
    super(props)

    this.state = {
      open: false,
Yannis Barlas's avatar
Yannis Barlas committed
      selected: props.value,
  toggleMenu = () => {
Yannis Barlas's avatar
Yannis Barlas committed
      open: !this.state.open,
  handleSelect = selected => {
Yannis Barlas's avatar
Yannis Barlas committed
      open: false,
Yannis Barlas's avatar
Yannis Barlas committed
      selected,
    this.props.onChange(selected)
  handleKeyPress = (event, selected) => {
    if (event.which === 13) {
      this.handleSelect(selected)
    }
  }

  optionLabel = value => {
    const { options } = this.props

    return options.find(option => option.value === value).label
  }

Yannis Barlas's avatar
Yannis Barlas committed
  render() {
Alf Eaton's avatar
Alf Eaton committed
    const { label, options, placeholder = 'Choose in the list' } = this.props
    const { open, selected } = this.state

    return (
      <Root open={open}>
        {label && <Label>{label}</Label>}

        <Main>
          <OpenerContainer>
            <Opener onClick={this.toggleMenu} open={open}>
              {selected ? (
                <span>{this.optionLabel(selected)}</span>
              ) : (
                <Placeholder>{placeholder}</Placeholder>
              <Arrow open={open}></Arrow>
            </Opener>
          </OpenerContainer>
          <OptionsContainer>
            {open && (
              <Options open={open}>
                {options.map(option => (
                  <Option
                    active={option.value === selected}
Yannis Barlas's avatar
Yannis Barlas committed
                    key={option.value}
                    onClick={() => this.handleSelect(option.value)}
                    onKeyPress={event =>
                      this.handleKeyPress(event, option.value)
                    }
Yannis Barlas's avatar
Yannis Barlas committed
                  >
                    {option.label || option.value}
          </OptionsContainer>
        </Main>
      </Root>