index.jsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import React from 'react'
  16. import styled from 'styled-components'
  17. import Palette from '../../styleUtils/Palette'
  18. import arrowImage from './images/arrow.svg'
  19. import checkmarkImage from './images/checkmark.svg'
  20. const Wrapper = styled.div`
  21. display: inline-block;
  22. position: relative;
  23. `
  24. const LinkButton = styled.div`
  25. display: flex;
  26. align-items: center;
  27. cursor: pointer;
  28. `
  29. const List = styled.div`
  30. position: absolute;
  31. top: 28px;
  32. right: -7px;
  33. z-index: 20;
  34. padding: 8px;
  35. background: ${Palette.grayscale[1]};
  36. border-radius: 4px;
  37. border: 1px solid ${Palette.grayscale[0]};
  38. width: 110px;
  39. &:after {
  40. content: ' ';
  41. position: absolute;
  42. top: -6px;
  43. right: 8px;
  44. width: 10px;
  45. height: 10px;
  46. background: ${Palette.grayscale[1]};
  47. border-top: 1px solid ${Palette.grayscale[0]};
  48. border-left: 1px solid ${Palette.grayscale[0]};
  49. border-bottom: 1px solid transparent;
  50. border-right: 1px solid transparent;
  51. transform: rotate(45deg);
  52. }
  53. `
  54. const ListItem = styled.div`
  55. padding-top: 13px;
  56. color: ${props => props.selected ? Palette.primary : Palette.grayscale[4]};
  57. cursor: pointer;
  58. display: flex;
  59. &:first-child {
  60. padding-top: 0;
  61. }
  62. `
  63. const ListItemLabel = styled.div``
  64. const Checkmark = styled.div`
  65. width: 16px;
  66. height: 16px;
  67. background: ${props => props.show ? `url('${checkmarkImage}') center no-repeat` : 'transparent'};
  68. margin-right: 8px;
  69. `
  70. const Label = styled.div`
  71. display: flex;
  72. justify-content: center;
  73. align-items: center;
  74. color: ${Palette.primary};
  75. `
  76. const Arrow = styled.div`
  77. width: 16px;
  78. height: 16px;
  79. background: url('${arrowImage}') center no-repeat;
  80. margin-left: 4px;
  81. margin-top: -1px;
  82. `
  83. type ItemType = {
  84. label: string,
  85. value: string
  86. }
  87. type Props = {
  88. selectedItem: string,
  89. items: ItemType[],
  90. onChange: (item: ItemType) => void
  91. }
  92. type State = {showDropdownList: boolean}
  93. class DropdownLink extends React.Component<Props, State> {
  94. itemMouseDown: boolean
  95. constructor() {
  96. super()
  97. this.state = {
  98. showDropdownList: false,
  99. }
  100. const self: any = this
  101. self.handlePageClick = this.handlePageClick.bind(this)
  102. }
  103. componentDidMount() {
  104. window.addEventListener('mousedown', this.handlePageClick, false)
  105. }
  106. componentWillUnmount() {
  107. window.removeEventListener('mousedown', this.handlePageClick, false)
  108. }
  109. handlePageClick() {
  110. if (!this.itemMouseDown) {
  111. this.setState({ showDropdownList: false })
  112. }
  113. }
  114. handleButtonClick() {
  115. this.setState({ showDropdownList: !this.state.showDropdownList })
  116. }
  117. handleItemClick(item: ItemType) {
  118. this.setState({ showDropdownList: false })
  119. if (this.props.onChange) {
  120. this.props.onChange(item)
  121. }
  122. }
  123. renderList() {
  124. if (!this.props.items || this.props.items.length === 0 || !this.state.showDropdownList) {
  125. return null
  126. }
  127. return (
  128. <List>
  129. {this.props.items.map((item) => {
  130. let listItem = (
  131. <ListItem
  132. key={item.label}
  133. onMouseDown={() => { this.itemMouseDown = true }}
  134. onMouseUp={() => { this.itemMouseDown = false }}
  135. onClick={() => { this.handleItemClick(item) }}
  136. selected={item.value === this.props.selectedItem}
  137. >
  138. <Checkmark show={item.value === this.props.selectedItem} />
  139. <ListItemLabel>{item.label}</ListItemLabel>
  140. </ListItem>
  141. )
  142. return listItem
  143. })}
  144. </List>
  145. )
  146. }
  147. render() {
  148. let selectedItem = this.props.items.find(i => i.value === this.props.selectedItem)
  149. return (
  150. <Wrapper>
  151. <LinkButton
  152. onMouseDown={() => { this.itemMouseDown = true }}
  153. onMouseUp={() => { this.itemMouseDown = false }}
  154. onClick={() => this.handleButtonClick()}
  155. >
  156. <Label>{selectedItem ? selectedItem.label : ''}</Label>
  157. <Arrow />
  158. </LinkButton>
  159. {this.renderList()}
  160. </Wrapper>
  161. )
  162. }
  163. }
  164. export default DropdownLink