You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

152 lines
3.2 KiB

  1. // Package imports.
  2. import classNames from 'classnames';
  3. import PropTypes from 'prop-types';
  4. import React from 'react';
  5. import {
  6. FormattedMessage,
  7. defineMessages,
  8. } from 'react-intl';
  9. import Overlay from 'react-overlays/lib/Overlay';
  10. // Components.
  11. import Icon from 'flavours/glitch/components/icon';
  12. import DrawerSearchPopout from './popout';
  13. // Utils.
  14. import { focusRoot } from 'flavours/glitch/util/dom_helpers';
  15. import {
  16. assignHandlers,
  17. hiddenComponent,
  18. } from 'flavours/glitch/util/react_helpers';
  19. // Messages.
  20. const messages = defineMessages({
  21. placeholder: {
  22. defaultMessage: 'Search',
  23. id: 'search.placeholder',
  24. },
  25. });
  26. // Handlers.
  27. const handlers = {
  28. handleBlur () {
  29. this.setState({ expanded: false });
  30. },
  31. handleChange ({ target: { value } }) {
  32. const { onChange } = this.props;
  33. if (onChange) {
  34. onChange(value);
  35. }
  36. },
  37. handleClear (e) {
  38. const {
  39. onClear,
  40. submitted,
  41. value,
  42. } = this.props;
  43. e.preventDefault(); // Prevents focus change ??
  44. if (onClear && (submitted || value && value.length)) {
  45. onClear();
  46. }
  47. },
  48. handleFocus () {
  49. const { onShow } = this.props;
  50. this.setState({ expanded: true });
  51. if (onShow) {
  52. onShow();
  53. }
  54. },
  55. handleKeyUp (e) {
  56. const { onSubmit } = this.props;
  57. switch (e.key) {
  58. case 'Enter':
  59. if (onSubmit) {
  60. onSubmit();
  61. }
  62. break;
  63. case 'Escape':
  64. focusRoot();
  65. }
  66. },
  67. };
  68. // The component.
  69. export default class DrawerSearch extends React.PureComponent {
  70. // Constructor.
  71. constructor (props) {
  72. super(props);
  73. assignHandlers(this, handlers);
  74. this.state = { expanded: false };
  75. }
  76. // Rendering.
  77. render () {
  78. const {
  79. handleBlur,
  80. handleChange,
  81. handleClear,
  82. handleFocus,
  83. handleKeyUp,
  84. } = this.handlers;
  85. const {
  86. intl,
  87. submitted,
  88. value,
  89. } = this.props;
  90. const { expanded } = this.state;
  91. const active = value && value.length || submitted;
  92. const computedClass = classNames('drawer--search', { active });
  93. return (
  94. <div className={computedClass}>
  95. <label>
  96. <span {...hiddenComponent}>
  97. <FormattedMessage {...messages.placeholder} />
  98. </span>
  99. <input
  100. type='text'
  101. placeholder={intl.formatMessage(messages.placeholder)}
  102. value={value || ''}
  103. onChange={handleChange}
  104. onKeyUp={handleKeyUp}
  105. onFocus={handleFocus}
  106. onBlur={handleBlur}
  107. />
  108. </label>
  109. <div
  110. aria-label={intl.formatMessage(messages.placeholder)}
  111. className='icon'
  112. onClick={handleClear}
  113. role='button'
  114. tabIndex='0'
  115. >
  116. <Icon icon='search' />
  117. <Icon icon='times-circle' />
  118. </div>
  119. <Overlay
  120. placement='bottom'
  121. show={expanded && !active}
  122. target={this}
  123. ><DrawerSearchPopout /></Overlay>
  124. </div>
  125. );
  126. }
  127. }
  128. // Props.
  129. DrawerSearch.propTypes = {
  130. value: PropTypes.string,
  131. submitted: PropTypes.bool,
  132. onChange: PropTypes.func,
  133. onSubmit: PropTypes.func,
  134. onClear: PropTypes.func,
  135. onShow: PropTypes.func,
  136. intl: PropTypes.object,
  137. };