index.jsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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 moment from 'moment'
  18. import type { NotificationItem } from '../../../types/NotificationItem'
  19. import Palette from '../../styleUtils/Palette'
  20. import StyleProps from '../../styleUtils/StyleProps'
  21. import bellImage from './images/bell.js'
  22. import errorImage from './images/error.svg'
  23. import infoImage from './images/info.svg'
  24. import successImage from './images/success.svg'
  25. const Wrapper = styled.div`
  26. cursor: pointer;
  27. position: relative;
  28. `
  29. const Icon = styled.div`
  30. position: relative;
  31. transition: all ${StyleProps.animations.swift};
  32. &:hover {
  33. opacity: 0.9;
  34. }
  35. `
  36. const BellIcon = styled.div``
  37. const Badge = styled.div`
  38. position: absolute;
  39. top: 0;
  40. right: 0;
  41. background: ${Palette.primary};
  42. border-radius: 50%;
  43. width: 14px;
  44. height: 14px;
  45. text-align: center;
  46. `
  47. const BadgeLabel = styled.div`
  48. margin-top: 2px;
  49. font-size: 10px;
  50. color: white;
  51. font-weight: ${StyleProps.fontWeights.medium};
  52. `
  53. const List = styled.div`
  54. cursor: pointer;
  55. background: ${Palette.grayscale[1]};
  56. border-radius: ${StyleProps.borderRadius};
  57. width: 224px;
  58. position: absolute;
  59. right: 0;
  60. top: 45px;
  61. z-index: 10;
  62. `
  63. const ListItem = styled.div`
  64. display: flex;
  65. border-bottom: 1px solid ${Palette.grayscale[0]};
  66. flex-direction: column;
  67. padding: 8px;
  68. transition: all ${StyleProps.animations.swift};
  69. &:hover {
  70. background: ${Palette.grayscale[0]};
  71. }
  72. &:first-child {
  73. position: relative;
  74. border-top-left-radius: ${StyleProps.borderRadius};
  75. border-top-right-radius: ${StyleProps.borderRadius};
  76. &:hover:after {
  77. background: ${Palette.grayscale[0]};
  78. border-color: transparent transparent ${Palette.grayscale[0]} ${Palette.grayscale[0]};
  79. }
  80. &:after {
  81. content: ' ';
  82. position: absolute;
  83. width: 10px;
  84. height: 10px;
  85. background: ${Palette.grayscale[1]};
  86. border: 1px solid ${Palette.grayscale[1]};
  87. border-color: transparent transparent ${Palette.grayscale[1]} ${Palette.grayscale[1]};
  88. transform: rotate(135deg);
  89. right: 10px;
  90. top: -6px;
  91. transition: all ${StyleProps.animations.swift};
  92. }
  93. }
  94. &:last-child {
  95. border-color: transparent;
  96. border-bottom-left-radius: ${StyleProps.borderRadius};
  97. border-bottom-right-radius: ${StyleProps.borderRadius};
  98. }
  99. `
  100. const Title = styled.div`
  101. display: flex;
  102. align-items: center;
  103. margin-bottom: 14px;
  104. `
  105. const getTypeIcon = level => {
  106. if (level === 'success') {
  107. return successImage
  108. }
  109. if (level === 'error') {
  110. return errorImage
  111. }
  112. return infoImage
  113. }
  114. const TypeIcon = styled.div`
  115. width: 16px;
  116. height: 16px;
  117. background: url('${props => getTypeIcon(props.level)}') no-repeat center;
  118. margin-right: 8px;
  119. `
  120. const TitleLabel = styled.div`flex-grow: 1;`
  121. const Time = styled.div`color: ${Palette.grayscale[4]};`
  122. const Description = styled.div``
  123. const NoItems = styled.div`
  124. text-align: center;
  125. `
  126. type Props = {
  127. white?: boolean,
  128. items: NotificationItem[],
  129. onClose: () => void,
  130. }
  131. type State = {
  132. showDropdownList: boolean,
  133. }
  134. class NotificationDropdown extends React.Component<Props, State> {
  135. itemMouseDown: boolean
  136. constructor() {
  137. super()
  138. this.state = {
  139. showDropdownList: false,
  140. }
  141. // $FlowIssue
  142. this.handlePageClick = this.handlePageClick.bind(this)
  143. }
  144. componentDidMount() {
  145. window.addEventListener('mousedown', this.handlePageClick, false)
  146. }
  147. componentWillUnmount() {
  148. window.removeEventListener('mousedown', this.handlePageClick, false)
  149. }
  150. handleItemClick() {
  151. this.setState({ showDropdownList: false })
  152. this.props.onClose()
  153. }
  154. handlePageClick() {
  155. if (!this.itemMouseDown) {
  156. if (this.state.showDropdownList) {
  157. this.props.onClose()
  158. }
  159. this.setState({ showDropdownList: false })
  160. }
  161. }
  162. handleButtonClick() {
  163. if (this.state.showDropdownList) {
  164. this.props.onClose()
  165. }
  166. this.setState({ showDropdownList: !this.state.showDropdownList })
  167. }
  168. renderNoItems() {
  169. if (!this.state.showDropdownList || (this.props.items && this.props.items.length > 0)) {
  170. return null
  171. }
  172. return (
  173. <List>
  174. <ListItem
  175. onMouseDown={() => { this.itemMouseDown = true }}
  176. onMouseUp={() => { this.itemMouseDown = false }}
  177. >
  178. <NoItems>There are no notifications</NoItems>
  179. </ListItem>
  180. </List>
  181. )
  182. }
  183. renderList() {
  184. if (!this.state.showDropdownList || !this.props.items || this.props.items.length === 0) {
  185. return null
  186. }
  187. let list = (
  188. <List>
  189. {this.props.items.map(item => {
  190. let title = (item.options && item.options.persistInfo && item.options.persistInfo.title) || item.message
  191. let message = title === item.message ? '' : item.message
  192. return (
  193. <ListItem
  194. key={item.id}
  195. onMouseDown={() => { this.itemMouseDown = true }}
  196. onMouseUp={() => { this.itemMouseDown = false }}
  197. onClick={() => { this.handleItemClick() }}
  198. >
  199. <Title>
  200. <TypeIcon level={item.level} />
  201. <TitleLabel>{title}</TitleLabel>
  202. <Time>{moment(item.id).format('HH:mm')}</Time>
  203. </Title>
  204. <Description>{message}</Description>
  205. </ListItem>
  206. )
  207. })}
  208. </List>
  209. )
  210. return list
  211. }
  212. renderBell() {
  213. let badge = this.props.items && this.props.items.length >= 1 ? (
  214. <Badge>
  215. <BadgeLabel>{this.props.items.length}</BadgeLabel>
  216. </Badge>
  217. ) : null
  218. return (
  219. <Icon
  220. onMouseDown={() => { this.itemMouseDown = true }}
  221. onMouseUp={() => { this.itemMouseDown = false }}
  222. onClick={() => this.handleButtonClick()}
  223. >
  224. <BellIcon
  225. dangerouslySetInnerHTML={{ __html: bellImage(this.props.white ? 'white' : Palette.grayscale[2]) }}
  226. />
  227. {badge}
  228. </Icon>
  229. )
  230. }
  231. render() {
  232. return (
  233. <Wrapper>
  234. {this.renderBell()}
  235. {this.renderList()}
  236. {this.renderNoItems()}
  237. </Wrapper>
  238. )
  239. }
  240. }
  241. export default NotificationDropdown