| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- /*
- Copyright (C) 2017 Cloudbase Solutions SRL
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- import React from 'react'
- import PropTypes from 'prop-types'
- import styled from 'styled-components'
- import ReactDOM from 'react-dom'
- import offset from 'document-offset'
- import { DropdownButton } from 'components'
- import Palette from '../../styleUtils/Palette'
- import StyleProps from '../../styleUtils/StyleProps'
- const Wrapper = styled.div`
- position: relative;
- `
- const getWidth = props => {
- if (props.large) {
- return StyleProps.inputSizes.large.width - 2
- }
- if (props.width) {
- return props.width - 2
- }
- return StyleProps.inputSizes.regular.width - 2
- }
- const List = styled.div`
- position: absolute;
- background: white;
- cursor: pointer;
- width: ${props => getWidth(props)}px;
- border: 1px solid ${Palette.grayscale[3]};
- border-radius: ${StyleProps.borderRadius};
- z-index: 1000;
- `
- const ListItems = styled.div`
- max-height: 400px;
- overflow: auto;
- `
- const Tip = styled.div`
- position: absolute;
- width: 10px;
- height: 10px;
- background: ${props => props.primary ? Palette.primary : 'white'};
- border-top: 1px solid ${Palette.grayscale[3]};
- border-left: 1px solid ${Palette.grayscale[3]};
- border-bottom: 1px solid ${props => props.primary ? Palette.primary : 'white'};
- border-right: 1px solid ${props => props.primary ? Palette.primary : 'white'};
- transform: rotate(45deg);
- right: 8px;
- top: -6px;
- z-index: 11;
- transition: all ${StyleProps.animations.swift};
- `
- const ListItem = styled.div`
- position: relative;
- color: ${Palette.grayscale[4]};
- padding: 8px 16px;
- transition: all ${StyleProps.animations.swift};
- ${props => props.selected ? `font-weight: ${StyleProps.fontWeights.medium};` : ''}
- &:first-child {
- border-top-left-radius: ${StyleProps.borderRadius};
- border-top-right-radius: ${StyleProps.borderRadius};
- }
- &:last-child {
- border-bottom-left-radius: ${StyleProps.borderRadius};
- border-bottom-right-radius: ${StyleProps.borderRadius};
- }
- &:hover {
- background: ${Palette.primary};
- color: white;
- }
- `
- class Dropdown extends React.Component {
- static propTypes = {
- selectedItem: PropTypes.any,
- items: PropTypes.array,
- labelField: PropTypes.string,
- className: PropTypes.string,
- onChange: PropTypes.func,
- noItemsMessage: PropTypes.string,
- noSelectionMessage: PropTypes.string,
- disabled: PropTypes.bool,
- width: PropTypes.number,
- }
- static defaultProps = {
- noSelectionMessage: 'Select an item',
- }
- constructor() {
- super()
- this.state = {
- showDropdownList: false,
- }
- this.handlePageClick = this.handlePageClick.bind(this)
- }
- componentDidMount() {
- window.addEventListener('mousedown', this.handlePageClick, false)
- }
- componentDidUpdate() {
- this.updateListPosition()
- }
- componentWillUnmount() {
- window.removeEventListener('mousedown', this.handlePageClick, false)
- }
- getLabel(item) {
- let labelField = this.props.labelField || 'label'
- if (item === null || item === undefined) {
- return this.props.noSelectionMessage
- }
- return (item[labelField] !== null && item[labelField] !== undefined && item[labelField].toString()) || item.toString()
- }
- updateListPosition() {
- if (!this.state.showDropdownList || !this.listRef || !this.buttonRef) {
- return
- }
- let buttonHeight = this.buttonRef.offsetHeight
- let tipHeight = 8
- let buttonOffset = offset(this.buttonRef)
- let listTop = buttonOffset.top + buttonHeight + tipHeight
- let listHeight = this.listRef.offsetHeight
- if (listTop + listHeight > window.innerHeight) {
- listTop = window.innerHeight - listHeight - 10
- this.tipRef.style.display = 'none'
- } else {
- this.tipRef.style.display = 'block'
- }
- this.listRef.style.top = `${listTop}px`
- this.listRef.style.left = `${buttonOffset.left}px`
- }
- handlePageClick() {
- if (!this.itemMouseDown) {
- this.setState({ showDropdownList: false })
- }
- }
- handleButtonClick() {
- if (this.props.disabled) {
- return
- }
- this.setState({ showDropdownList: !this.state.showDropdownList })
- }
- handleItemClick(item) {
- this.setState({ showDropdownList: false, firstItemHover: false })
- if (this.props.onChange) {
- this.props.onChange(item)
- }
- }
- handleItemMouseEnter(index) {
- if (index === 0) {
- this.setState({ firstItemHover: true })
- }
- }
- handleItemMouseLeave(index) {
- if (index === 0) {
- this.setState({ firstItemHover: false })
- }
- }
- renderList() {
- if (!this.props.items || this.props.items.length === 0 || !this.state.showDropdownList) {
- return null
- }
- let selectedLabel = this.getLabel(this.props.selectedItem)
- let list = ReactDOM.createPortal((
- <List {...this.props} innerRef={ref => { this.listRef = ref }}>
- <Tip innerRef={ref => { this.tipRef = ref }} primary={this.state.firstItemHover} />
- <ListItems>
- {this.props.items.map((item, i) => {
- let label = this.getLabel(item)
- let listItem = (
- <ListItem
- key={label}
- onMouseDown={() => { this.itemMouseDown = true }}
- onMouseUp={() => { this.itemMouseDown = false }}
- onMouseEnter={() => { this.handleItemMouseEnter(i) }}
- onMouseLeave={() => { this.handleItemMouseLeave(i) }}
- onClick={() => { this.handleItemClick(item) }}
- selected={label === selectedLabel}
- >{label}
- </ListItem>
- )
- return listItem
- })}
- </ListItems>
- </List>
- ), document.body)
- return list
- }
- render() {
- let buttonValue = () => {
- if (this.props.items && this.props.items.length) {
- return this.getLabel(this.props.selectedItem)
- }
- return this.props.noItemsMessage || ''
- }
- return (
- <Wrapper
- className={this.props.className}
- onMouseDown={() => { this.itemMouseDown = true }}
- onMouseUp={() => { this.itemMouseDown = false }}
- >
- <DropdownButton
- {...this.props}
- innerRef={ref => { this.buttonRef = ref }}
- onMouseDown={() => { this.itemMouseDown = true }}
- onMouseUp={() => { this.itemMouseDown = false }}
- value={buttonValue()}
- onClick={() => this.handleButtonClick()}
- />
- {this.renderList()}
- </Wrapper>
- )
- }
- }
- export default Dropdown
|