Browse Source

fix(replay): Improve replay search box breakpoints so placeholder text does not wrap (#57037)

Before it was very easy for the placeholder text inside the search box
to wrap, like this:

![SCR-20230927-jftj](https://github.com/getsentry/sentry/assets/187460/946fd5bd-5b8c-4ded-9e95-d87443f301d6)


Now it wraps less often, in english only at the smallest of browser
window sizes.
| Before breakpoint | After breakpoint | 
| --- | --- |
|
![SCR-20230927-jehd](https://github.com/getsentry/sentry/assets/187460/6df40e4e-c231-441b-b6ad-5b04da297da7)
|
![SCR-20230927-jntj](https://github.com/getsentry/sentry/assets/187460/f7de2e28-5446-4b30-9c9d-769e0c5e4f04)
|
|
![SCR-20230927-jnuo](https://github.com/getsentry/sentry/assets/187460/89520bbb-14a2-40a2-a49f-16b60e64df74)
|
![SCR-20230927-jeim](https://github.com/getsentry/sentry/assets/187460/f49a9ab1-5cc5-4361-ad81-2ba7d7bd3087)
|
|
![SCR-20230927-jekh](https://github.com/getsentry/sentry/assets/187460/3c0dc41f-44c6-4723-9557-2e65d0622490)
|
![SCR-20230927-jeld](https://github.com/getsentry/sentry/assets/187460/12104fff-bd6b-4d03-b9fa-ffff3479fea2)
|
| n/a |
![SCR-20230927-jewl](https://github.com/getsentry/sentry/assets/187460/44db56ae-3816-4a05-9255-c532c0c201b3)
|



Fixes https://github.com/getsentry/sentry/issues/57030
Ryan Albrecht 1 year ago
parent
commit
a0a3756158

+ 8 - 12
static/app/components/smartSearchBar/index.tsx

@@ -1,7 +1,5 @@
 import {Component, createRef, VFC} from 'react';
-import TextareaAutosize from 'react-autosize-textarea';
 import {WithRouterProps} from 'react-router';
-import isPropValid from '@emotion/is-prop-valid';
 import styled from '@emotion/styled';
 import * as Sentry from '@sentry/react';
 import debounce from 'lodash/debounce';
@@ -48,7 +46,7 @@ import {
   FieldValueType,
   getFieldDefinition,
 } from 'sentry/utils/fields';
-import getDynamicComponent from 'sentry/utils/getDynamicComponent';
+import SearchBoxTextArea from 'sentry/utils/search/searchBoxTextArea';
 import withApi from 'sentry/utils/withApi';
 import withOrganization from 'sentry/utils/withOrganization';
 // eslint-disable-next-line no-restricted-imports
@@ -1896,6 +1894,7 @@ class SmartSearchBar extends Component<DefaultProps & Props, State> {
         disabled={disabled}
         maxLength={maxQueryLength}
         spellCheck={false}
+        maxRows={query ? undefined : 1}
       />
     );
 
@@ -2110,15 +2109,7 @@ const Highlight = styled('div')`
   font-family: ${p => p.theme.text.familyMono};
 `;
 
-const SearchInput = styled(
-  getDynamicComponent<typeof TextareaAutosize>({
-    value: TextareaAutosize,
-    fixed: 'textarea',
-  }),
-  {
-    shouldForwardProp: prop => typeof prop === 'string' && isPropValid(prop),
-  }
-)`
+const SearchInput = styled(SearchBoxTextArea)`
   position: relative;
   display: flex;
   resize: none;
@@ -2140,6 +2131,11 @@ const SearchInput = styled(
   &::placeholder {
     color: ${p => p.theme.formPlaceholder};
   }
+  :placeholder-shown {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
 
   [disabled] {
     color: ${p => p.theme.disabled};

+ 0 - 15
static/app/utils/getDynamicComponent.tsx

@@ -1,15 +0,0 @@
-import {FunctionComponent} from 'react';
-
-/**
- * Returns a replacement component, this function is mocked in tests and will use the second argument.
- * (This only happens during tests)
- */
-export default function getDynamicComponent<Original extends FunctionComponent>({
-  value,
-}: {
-  fixed: 'textarea';
-  value: Original;
-}): Original {
-  // Overridden with fixed in tests.
-  return value;
-}

+ 5 - 0
static/app/utils/search/__mocks__/searchBoxTextArea.tsx

@@ -0,0 +1,5 @@
+/**
+ * Mock of TextareaAutosize
+ */
+const SearchBoxTextArea = 'textarea';
+export default SearchBoxTextArea;

+ 8 - 0
static/app/utils/search/searchBoxTextArea.tsx

@@ -0,0 +1,8 @@
+import TextareaAutosize from 'react-autosize-textarea';
+
+const SearchBoxTextArea = TextareaAutosize;
+
+/**
+ * Returns TextareaAutosize in prod, but is mocked with 'textarea' for unit tests.
+ */
+export default SearchBoxTextArea;

+ 0 - 7
static/app/views/performance/trends/index.spec.tsx

@@ -31,13 +31,6 @@ const trendsViewQuery = {
   query: `tpm():>0.01 transaction.duration:>0 transaction.duration:<${DEFAULT_MAX_DURATION}`,
 };
 
-jest.mock(
-  'sentry/utils/getDynamicComponent',
-  () =>
-    ({fixed}) =>
-      fixed
-);
-
 jest.mock('moment', () => {
   const moment = jest.requireActual('moment');
   moment.now = jest.fn().mockReturnValue(1601251200000);

+ 14 - 6
static/app/views/replays/list/listContent.tsx

@@ -75,12 +75,14 @@ export default function ListContent() {
     <Fragment>
       <FiltersContainer>
         <ReplaysFilters />
-        <ReplaysSearch />
-        {hasdeadRageClickFeature ? (
-          <Button onClick={() => setWidgetIsOpen(!widgetIsOpen)}>
-            {widgetIsOpen ? t('Hide Widgets') : t('Show Widgets')}
-          </Button>
-        ) : null}
+        <SearchWrapper>
+          <ReplaysSearch />
+          {hasdeadRageClickFeature ? (
+            <Button onClick={() => setWidgetIsOpen(!widgetIsOpen)}>
+              {widgetIsOpen ? t('Hide Widgets') : t('Show Widgets')}
+            </Button>
+          ) : null}
+        </SearchWrapper>
       </FiltersContainer>
       {hasdeadRageClickFeature ? (
         widgetIsOpen ? (
@@ -98,4 +100,10 @@ const FiltersContainer = styled('div')`
   display: flex;
   flex-direction: row;
   gap: ${space(2)};
+  flex-wrap: wrap;
+`;
+
+const SearchWrapper = styled(FiltersContainer)`
+  flex-grow: 1;
+  flex-wrap: nowrap;
 `;

+ 6 - 2
static/app/views/replays/list/search.tsx

@@ -35,6 +35,10 @@ export default function ReplaysSearch() {
 }
 
 const SearchContainer = styled('div')`
-  display: grid;
-  width: 100%;
+  flex-grow: 1;
+  min-width: 400px;
+
+  @media (max-width: ${p => p.theme.breakpoints.small}) {
+    min-width: auto;
+  }
 `;

+ 1 - 0
tests/js/setup.ts

@@ -75,6 +75,7 @@ jest.mock('react-router', function reactRouterMockFactory() {
     },
   };
 });
+jest.mock('sentry/utils/search/searchBoxTextArea');
 
 jest.mock('react-virtualized', function reactVirtualizedMockFactory() {
   const ActualReactVirtualized = jest.requireActual('react-virtualized');