UserDropdown.jsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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 { observer } from 'mobx-react'
  17. import styled, { css } from 'styled-components'
  18. import Palette from '../../styleUtils/Palette'
  19. import StyleProps from '../../styleUtils/StyleProps'
  20. import { navigationMenu } from '../../../config'
  21. import type { User } from '../../../types/User'
  22. import userImage from './images/user.svg'
  23. import userWhiteImage from './images/user-white.svg'
  24. const Wrapper = styled.div`
  25. position: relative;
  26. `
  27. const Icon = styled.div`
  28. position: relative;
  29. cursor: pointer;
  30. width: 32px;
  31. height: 32px;
  32. transition: all ${StyleProps.animations.swift};
  33. background: url('${props => props.white ? userWhiteImage : userImage}') no-repeat center;
  34. &:hover {
  35. opacity: 0.8;
  36. }
  37. `
  38. const List = styled.div`
  39. background: ${Palette.grayscale[1]};
  40. border-radius: ${StyleProps.borderRadius};
  41. position: absolute;
  42. right: 0;
  43. top: 45px;
  44. padding: 16px;
  45. display: flex;
  46. flex-direction: column;
  47. z-index: 10;
  48. `
  49. const ListItem = styled.div`
  50. padding: 8px 0;
  51. &:last-child {
  52. padding-bottom: 0;
  53. }
  54. `
  55. const Label = styled.div`
  56. display: inline-block;
  57. white-space: nowrap;
  58. ${props => props.selectable ? css`
  59. cursor: pointer;
  60. &:hover {
  61. color: ${Palette.primary};
  62. }
  63. ` : ''}
  64. `
  65. const ListHeader = styled.div`
  66. position: relative;
  67. &:after {
  68. content: ' ';
  69. position: absolute;
  70. width: 10px;
  71. height: 10px;
  72. background: ${Palette.grayscale[1]};
  73. border: 1px solid ${Palette.grayscale[1]};
  74. border-color: transparent transparent ${Palette.grayscale[1]} ${Palette.grayscale[1]};
  75. transform: rotate(135deg);
  76. right: -6px;
  77. top: -22px;
  78. transition: all ${StyleProps.animations.swift};
  79. }
  80. `
  81. const Username = styled.a`
  82. font-size: 16px;
  83. color: ${Palette.black};
  84. text-decoration: none;
  85. &:hover {
  86. color: ${props => props.href ? Palette.primary : 'inherit'};
  87. }
  88. `
  89. const Email = styled.div`
  90. font-size: 10px;
  91. color: ${Palette.grayscale[4]};
  92. margin-top: 8px;
  93. padding-bottom: 8px;
  94. border-bottom: 1px solid ${Palette.grayscale[3]};
  95. `
  96. type DictItem = { label: string, value: string }
  97. type Props = {
  98. onItemClick: (item: DictItem) => void,
  99. user: ?User,
  100. white?: boolean,
  101. }
  102. type State = {
  103. showDropdownList: boolean,
  104. }
  105. @observer
  106. class UserDropdown extends React.Component<Props, State> {
  107. itemMouseDown: boolean
  108. constructor() {
  109. super()
  110. this.state = {
  111. showDropdownList: false,
  112. }
  113. // $FlowIssue
  114. this.handlePageClick = this.handlePageClick.bind(this)
  115. }
  116. componentDidMount() {
  117. window.addEventListener('mousedown', this.handlePageClick, false)
  118. }
  119. componentWillUnmount() {
  120. window.removeEventListener('mousedown', this.handlePageClick, false)
  121. }
  122. handleItemClick(item: DictItem) {
  123. if (this.props.onItemClick) {
  124. this.props.onItemClick(item)
  125. }
  126. this.setState({ showDropdownList: false })
  127. }
  128. handlePageClick() {
  129. if (!this.itemMouseDown) {
  130. this.setState({ showDropdownList: false })
  131. }
  132. }
  133. handleButtonClick() {
  134. this.setState({ showDropdownList: !this.state.showDropdownList })
  135. }
  136. renderNoUser() {
  137. if (this.props.user) {
  138. return null
  139. }
  140. return <Label>No signed in user</Label>
  141. }
  142. renderListHeader() {
  143. if (!this.props.user) {
  144. return null
  145. }
  146. let href: ?string
  147. let isAdmin = this.props.user.isAdmin
  148. if (isAdmin && navigationMenu.find(m => m.value === 'users' && !m.disabled && (!m.requiresAdmin || isAdmin))) {
  149. href = `#/user/${this.props.user.id}`
  150. }
  151. return (
  152. <ListHeader
  153. onMouseDown={() => { this.itemMouseDown = true }}
  154. onMouseUp={() => { this.itemMouseDown = false }}
  155. >
  156. <Username
  157. data-test-id="userDropdown-username"
  158. href={href}
  159. >{this.props.user.name}</Username>
  160. <Email>{this.props.user.email}</Email>
  161. </ListHeader>
  162. )
  163. }
  164. renderList() {
  165. if (!this.state.showDropdownList) {
  166. return null
  167. }
  168. let items = [{
  169. label: 'Sign Out',
  170. value: 'signout',
  171. }]
  172. let list = (
  173. <List>
  174. <ListHeader>
  175. {this.renderListHeader()}
  176. {this.renderNoUser()}
  177. </ListHeader>
  178. {this.props.user ? items.map(item => {
  179. return (
  180. <ListItem
  181. key={item.value}
  182. onMouseDown={() => { this.itemMouseDown = true }}
  183. onMouseUp={() => { this.itemMouseDown = false }}
  184. >
  185. <Label selectable onClick={() => { this.handleItemClick(item) }} data-test-id={`userDropdown-label-${item.value}`}>{item.label}</Label>
  186. </ListItem>
  187. )
  188. }) : null}
  189. </List>
  190. )
  191. return list
  192. }
  193. render() {
  194. return (
  195. <Wrapper {...this.props}>
  196. <Icon
  197. onMouseDown={() => { this.itemMouseDown = true }}
  198. onMouseUp={() => { this.itemMouseDown = false }}
  199. onClick={() => this.handleButtonClick()}
  200. white={this.props.white}
  201. data-test-id="userDropdown-button"
  202. />
  203. {this.renderList()}
  204. </Wrapper>
  205. )
  206. }
  207. }
  208. export default UserDropdown