UserDropdown.jsx 5.6 KB

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