plainTextQueryInput.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import {
  2. type ChangeEvent,
  3. type KeyboardEvent,
  4. type SyntheticEvent,
  5. useCallback,
  6. useRef,
  7. useState,
  8. } from 'react';
  9. import styled from '@emotion/styled';
  10. import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context';
  11. import HighlightQuery from 'sentry/components/searchSyntax/renderer';
  12. import {space} from 'sentry/styles/space';
  13. interface PlainTextQueryInputProps {
  14. label?: string;
  15. }
  16. export function PlainTextQueryInput({label}: PlainTextQueryInputProps) {
  17. const inputRef = useRef<HTMLTextAreaElement>(null);
  18. const {query, parsedQuery, dispatch, handleSearch, size, placeholder, disabled} =
  19. useSearchQueryBuilder();
  20. const [cursorPosition, setCursorPosition] = useState(0);
  21. const setCursorPositionOnEvent = (event: SyntheticEvent<HTMLTextAreaElement>) => {
  22. if (event.currentTarget !== document.activeElement) {
  23. setCursorPosition(-1);
  24. } else {
  25. setCursorPosition(event.currentTarget.selectionStart);
  26. }
  27. };
  28. const onChange = useCallback(
  29. (e: ChangeEvent<HTMLTextAreaElement>) => {
  30. setCursorPositionOnEvent(e);
  31. dispatch({type: 'UPDATE_QUERY', query: e.target.value});
  32. },
  33. [dispatch]
  34. );
  35. const onKeyDown = useCallback(
  36. (e: KeyboardEvent<HTMLTextAreaElement>) => {
  37. setCursorPositionOnEvent(e);
  38. if (e.key === 'Enter') {
  39. e.preventDefault();
  40. handleSearch(query);
  41. }
  42. },
  43. [handleSearch, query]
  44. );
  45. return (
  46. <InputWrapper>
  47. {parsedQuery ? (
  48. <Highlight size={size}>
  49. <HighlightQuery parsedQuery={parsedQuery} cursorPosition={cursorPosition} />
  50. </Highlight>
  51. ) : null}
  52. <InvisibleInput
  53. aria-label={label}
  54. ref={inputRef}
  55. autoComplete="off"
  56. value={query}
  57. onFocus={setCursorPositionOnEvent}
  58. onBlur={setCursorPositionOnEvent}
  59. onKeyUp={setCursorPositionOnEvent}
  60. onKeyDown={onKeyDown}
  61. onChange={onChange}
  62. onClick={setCursorPositionOnEvent}
  63. onPaste={setCursorPositionOnEvent}
  64. spellCheck={false}
  65. size={size}
  66. placeholder={placeholder}
  67. disabled={disabled}
  68. />
  69. </InputWrapper>
  70. );
  71. }
  72. const InputWrapper = styled('div')`
  73. position: relative;
  74. width: 100%;
  75. height: 100%;
  76. `;
  77. const Highlight = styled('div')<{size: 'small' | 'normal'}>`
  78. padding: ${p =>
  79. p.size === 'small'
  80. ? `${space(0.75)} ${space(1)}`
  81. : `${space(0.75)} 48px ${space(0.75)} 44px`};
  82. width: 100%;
  83. height: 100%;
  84. user-select: none;
  85. white-space: pre-wrap;
  86. word-break: break-word;
  87. line-height: 24px;
  88. font-size: ${p => p.theme.fontSizeSmall};
  89. font-family: ${p => p.theme.text.familyMono};
  90. `;
  91. const InvisibleInput = styled('textarea')<{size: 'small' | 'normal'}>`
  92. padding: ${p =>
  93. p.size === 'small'
  94. ? `${space(0.75)} ${space(1)}`
  95. : `${space(0.75)} 48px ${space(0.75)} 44px`};
  96. position: absolute;
  97. inset: 0;
  98. resize: none;
  99. outline: none;
  100. border: 0;
  101. width: 100%;
  102. line-height: 25px;
  103. margin-bottom: -1px;
  104. background: transparent;
  105. font-size: ${p => p.theme.fontSizeSmall};
  106. font-family: ${p => p.theme.text.familyMono};
  107. caret-color: ${p => p.theme.subText};
  108. color: transparent;
  109. &::selection {
  110. background: rgba(0, 0, 0, 0.2);
  111. }
  112. &::placeholder {
  113. color: ${p => p.theme.formPlaceholder};
  114. }
  115. :placeholder-shown {
  116. overflow: hidden;
  117. text-overflow: ellipsis;
  118. white-space: nowrap;
  119. }
  120. [disabled] {
  121. color: ${p => p.theme.disabled};
  122. }
  123. `;