index.jsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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, { css } from 'styled-components'
  17. import SearchButton from '../../atoms/SearchButton'
  18. import TextInput from '../../atoms/TextInput'
  19. import StatusIcon from '../../atoms/StatusIcon'
  20. import StyleProps from '../../styleUtils/StyleProps'
  21. const Input = styled(TextInput) `
  22. position: absolute;
  23. top: -8px;
  24. left: -8px;
  25. padding-left: 32px;
  26. ${props => props.loading ? 'padding-right: 32px;' : ''}
  27. width: 50px;
  28. opacity: 0;
  29. transition: all ${StyleProps.animations.swift};
  30. `
  31. const InputAnimation = css`
  32. ${Input} {
  33. width: ${StyleProps.inputSizes.regular.width}px;
  34. opacity: 1;
  35. }
  36. `
  37. const Wrapper = styled.div`
  38. position: relative;
  39. ${props => props.open ? InputAnimation : ''}
  40. `
  41. const SearchButtonStyled = styled(SearchButton)`
  42. position: relative;
  43. `
  44. const StatusIconStyled = styled(StatusIcon)`
  45. position: absolute;
  46. left: 144px;
  47. top: 0;
  48. `
  49. type Props = {
  50. onChange: (value: string) => void,
  51. alwaysOpen?: boolean,
  52. loading?: boolean,
  53. placeholder: string,
  54. }
  55. type State = {
  56. open: boolean,
  57. hover?: boolean,
  58. focus?: boolean,
  59. value: string,
  60. }
  61. class SearchInput extends React.Component<Props, State> {
  62. static defaultProps = {
  63. placeholder: 'Search',
  64. }
  65. input: HTMLElement
  66. itemMouseDown: boolean
  67. constructor() {
  68. super()
  69. this.state = {
  70. open: false,
  71. value: '',
  72. }
  73. // $FlowIssue
  74. this.handlePageClick = this.handlePageClick.bind(this)
  75. }
  76. componentDidMount() {
  77. window.addEventListener('mousedown', this.handlePageClick, false)
  78. }
  79. componentWillUnmount() {
  80. window.removeEventListener('mousedown', this.handlePageClick, false)
  81. }
  82. handlePageClick() {
  83. if (!this.itemMouseDown) {
  84. this.setState({ open: false })
  85. }
  86. }
  87. handleSearchButtonClick() {
  88. this.input && this.input.focus()
  89. this.setState({ open: !this.state.open })
  90. }
  91. handleMouseEnter() {
  92. this.setState({ hover: true })
  93. }
  94. handleMouseLeave() {
  95. this.setState({ hover: false })
  96. }
  97. handleFocus() {
  98. this.setState({ focus: true })
  99. }
  100. handleBlur() {
  101. this.setState({ focus: false })
  102. }
  103. render() {
  104. return (
  105. <Wrapper
  106. open={this.state.open || this.props.alwaysOpen || this.state.value !== ''}
  107. onMouseDown={() => { this.itemMouseDown = true }}
  108. onMouseUp={() => { this.itemMouseDown = false }}
  109. onMouseEnter={() => { this.handleMouseEnter() }}
  110. onMouseLeave={() => { this.handleMouseLeave() }}
  111. >
  112. <Input
  113. _ref={input => { this.input = input }}
  114. placeholder={this.props.placeholder}
  115. onChange={e => {
  116. this.setState({ value: e.target.value })
  117. this.props.onChange(e.target.value)
  118. }}
  119. value={this.state.value}
  120. onFocus={() => { this.handleFocus() }}
  121. onBlur={() => { this.handleBlur() }}
  122. loading={this.props.loading}
  123. />
  124. <SearchButtonStyled
  125. primary={
  126. this.state.open ||
  127. (this.props.alwaysOpen && (this.state.hover || this.state.focus)) ||
  128. (this.state.value !== '' && (this.state.hover || this.state.focus))
  129. }
  130. onClick={() => { this.handleSearchButtonClick() }}
  131. />
  132. {this.props.loading ? <StatusIconStyled status="RUNNING" /> : null}
  133. </Wrapper>
  134. )
  135. }
  136. }
  137. export default SearchInput