|
|
@@ -12,12 +12,13 @@ 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/>.
|
|
|
*/
|
|
|
|
|
|
+// @flow
|
|
|
+
|
|
|
import React from 'react'
|
|
|
-import PropTypes from 'prop-types'
|
|
|
import styled from 'styled-components'
|
|
|
import ReactDOM from 'react-dom'
|
|
|
|
|
|
-import { DropdownButton } from 'components'
|
|
|
+import DropdownButton from '../../atoms/DropdownButton'
|
|
|
|
|
|
import Palette from '../../styleUtils/Palette'
|
|
|
import StyleProps from '../../styleUtils/StyleProps'
|
|
|
@@ -87,31 +88,42 @@ const ListItem = styled.div`
|
|
|
}
|
|
|
`
|
|
|
|
|
|
-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 = {
|
|
|
+type Props = {
|
|
|
+ selectedItem: any,
|
|
|
+ items: any[],
|
|
|
+ labelField: string,
|
|
|
+ className: string,
|
|
|
+ onChange: (item: any) => void,
|
|
|
+ noItemsMessage: string,
|
|
|
+ noSelectionMessage: string,
|
|
|
+ disabled: boolean,
|
|
|
+ width: number,
|
|
|
+}
|
|
|
+type State = {
|
|
|
+ showDropdownList: boolean,
|
|
|
+ firstItemHover: boolean
|
|
|
+}
|
|
|
+class Dropdown extends React.Component<Props, State> {
|
|
|
+ static defaultProps: $Shape<Props> = {
|
|
|
noSelectionMessage: 'Select an item',
|
|
|
}
|
|
|
|
|
|
+ buttonRef: HTMLElement
|
|
|
+ listRef: HTMLElement
|
|
|
+ tipRef: HTMLElement
|
|
|
+ buttonRect: ClientRect
|
|
|
+ itemMouseDown: boolean
|
|
|
+
|
|
|
constructor() {
|
|
|
super()
|
|
|
|
|
|
this.state = {
|
|
|
showDropdownList: false,
|
|
|
+ firstItemHover: false,
|
|
|
}
|
|
|
|
|
|
- this.handlePageClick = this.handlePageClick.bind(this)
|
|
|
+ const self: any = this
|
|
|
+ self.handlePageClick = this.handlePageClick.bind(this)
|
|
|
}
|
|
|
|
|
|
componentDidMount() {
|
|
|
@@ -131,7 +143,7 @@ class Dropdown extends React.Component {
|
|
|
window.removeEventListener('mousedown', this.handlePageClick, false)
|
|
|
}
|
|
|
|
|
|
- getLabel(item) {
|
|
|
+ getLabel(item: any) {
|
|
|
let labelField = this.props.labelField || 'label'
|
|
|
|
|
|
if (item === null || item === undefined) {
|
|
|
@@ -141,33 +153,6 @@ class Dropdown extends React.Component {
|
|
|
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 listTop = this.buttonRect.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'
|
|
|
- }
|
|
|
-
|
|
|
- // If a modal is opened, body scroll is removed and body top is set to replicate scroll position
|
|
|
- let scrollOffset = 0
|
|
|
- if (parseInt(document.body.style.top, 10) < 0) {
|
|
|
- scrollOffset = -parseInt(document.body.style.top, 10)
|
|
|
- }
|
|
|
-
|
|
|
- this.listRef.style.top = `${listTop + (window.pageYOffset || scrollOffset)}px`
|
|
|
- this.listRef.style.left = `${this.buttonRect.left}px`
|
|
|
- }
|
|
|
-
|
|
|
handlePageClick() {
|
|
|
if (!this.itemMouseDown) {
|
|
|
this.setState({ showDropdownList: false })
|
|
|
@@ -182,7 +167,7 @@ class Dropdown extends React.Component {
|
|
|
this.setState({ showDropdownList: !this.state.showDropdownList })
|
|
|
}
|
|
|
|
|
|
- handleItemClick(item) {
|
|
|
+ handleItemClick(item: any) {
|
|
|
this.setState({ showDropdownList: false, firstItemHover: false })
|
|
|
|
|
|
if (this.props.onChange) {
|
|
|
@@ -190,23 +175,51 @@ class Dropdown extends React.Component {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- handleItemMouseEnter(index) {
|
|
|
+ handleItemMouseEnter(index: number) {
|
|
|
if (index === 0) {
|
|
|
this.setState({ firstItemHover: true })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- handleItemMouseLeave(index) {
|
|
|
+ handleItemMouseLeave(index: number) {
|
|
|
if (index === 0) {
|
|
|
this.setState({ firstItemHover: false })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ updateListPosition() {
|
|
|
+ if (!this.state.showDropdownList || !this.listRef || !this.buttonRef || !document.body) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let buttonHeight = this.buttonRef.offsetHeight
|
|
|
+ let tipHeight = 8
|
|
|
+ let listTop = this.buttonRect.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'
|
|
|
+ }
|
|
|
+
|
|
|
+ // If a modal is opened, body scroll is removed and body top is set to replicate scroll position
|
|
|
+ let scrollOffset = 0
|
|
|
+ if (parseInt(document.body.style.top, 10) < 0) {
|
|
|
+ scrollOffset = -parseInt(document.body && document.body.style.top, 10)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.listRef.style.top = `${listTop + (window.pageYOffset || scrollOffset)}px`
|
|
|
+ this.listRef.style.left = `${this.buttonRect.left}px`
|
|
|
+ }
|
|
|
+
|
|
|
renderList() {
|
|
|
if (!this.props.items || this.props.items.length === 0 || !this.state.showDropdownList) {
|
|
|
return null
|
|
|
}
|
|
|
|
|
|
+ const body: any = document.body
|
|
|
let selectedLabel = this.getLabel(this.props.selectedItem)
|
|
|
let list = ReactDOM.createPortal((
|
|
|
<List {...this.props} innerRef={ref => { this.listRef = ref }}>
|
|
|
@@ -231,7 +244,7 @@ class Dropdown extends React.Component {
|
|
|
})}
|
|
|
</ListItems>
|
|
|
</List>
|
|
|
- ), document.body)
|
|
|
+ ), body)
|
|
|
|
|
|
return list
|
|
|
}
|