/* Copyright (C) 2017 Cloudbase Solutions SRL This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import React from 'react' import { observer } from 'mobx-react' import styled, { css } from 'styled-components' import autobind from 'autobind-decorator' import SearchButton from '@src/components/ui/SearchButton' import TextInput from '@src/components/ui/TextInput' import StatusIcon from '@src/components/ui/StatusComponents/StatusIcon' import { ThemeProps } from '@src/components/Theme' const Input = styled(TextInput)` padding-left: 32px; ${props => (props.loading || (props.showClose && props.value) ? 'padding-right: 32px;' : '')} width: 50px; opacity: 0; transition: all ${ThemeProps.animations.swift}; ` const InputAnimation = (props: any) => css` ${Input} { width: ${props.width}; opacity: 1; } ` const Wrapper = styled.div` position: relative; width: ${props => (props.open ? props.width : '50px')}; ${props => (props.open ? InputAnimation(props) : '')} ` const SearchButtonStyled = styled(SearchButton)` position: absolute; top: 8px; left: 8px; ` const StatusIconStyled = styled(StatusIcon)` position: absolute; right: 8px; top: 8px; ` type Props = { onChange?: (value: string) => void, onCloseClick?: () => void, alwaysOpen?: boolean, loading?: boolean, focusOnMount?: boolean, disablePrimary?: boolean, useFilterIcon?: boolean, placeholder?: string, width?: string, value?: string, className?: string, } type State = { open: boolean, hover?: boolean, focus: boolean, } @observer class SearchInput extends React.Component { static defaultProps = { placeholder: 'Search', width: `${ThemeProps.inputSizes.regular.width}px`, value: '', } state: State = { open: false, focus: false, } input: HTMLElement | null | undefined itemMouseDown: boolean | undefined componentDidMount() { window.addEventListener('mousedown', this.handlePageClick, false) if (this.props.focusOnMount && this.input) this.input.focus() } componentWillUnmount() { window.removeEventListener('mousedown', this.handlePageClick, false) } @autobind handlePageClick() { if (!this.itemMouseDown) { this.setState({ open: false }) } } handleSearchButtonClick() { if (this.input) this.input.focus() this.setState(prevState => ({ open: !prevState.open })) } handleMouseEnter() { this.setState({ hover: true }) } handleMouseLeave() { this.setState({ hover: false }) } handleFocus() { this.setState({ focus: true }) } handleBlur() { this.setState({ focus: false }) } render() { return ( { this.itemMouseDown = true }} onMouseUp={() => { this.itemMouseDown = false }} onMouseEnter={() => { this.handleMouseEnter() }} onMouseLeave={() => { this.handleMouseLeave() }} width={this.props.width} className={this.props.className} > { this.input = input }} placeholder={this.props.placeholder} onChange={(e: { target: { value: string } }) => { if (this.props.onChange) this.props.onChange(e.target.value) }} onFocus={() => { this.handleFocus() }} onBlur={() => { this.handleBlur() }} loading={this.props.loading} value={this.props.value} disablePrimary={this.props.disablePrimary} showClose={ !this.props.loading && (this.state.open || this.props.alwaysOpen || this.props.value !== '') } onCloseClick={() => { if (this.props.onCloseClick) this.props.onCloseClick() }} /> { this.handleSearchButtonClick() }} useFilterIcon={this.props.useFilterIcon} /> {this.props.loading ? : null} ) } } export default SearchInput