2
0

index.jsx 4.7 KB

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