mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
add LazyQueryInputFormControl, remove duplicated common pattern (#63537)
Many places used a LazyQueryInput with a focus ring and implemented it incorrectly: - The text would overflow - The tooltips in the query input would be chopped off This cleans up duplicate code and fixes those bugs. No visual changes. ## Test plan View the query input and try overflowing it on: - search context query input - saved search query input - code monitor query input
This commit is contained in:
parent
aa87316d68
commit
1b9459cab7
@ -3,18 +3,20 @@
|
||||
// Match query input styling for the intermediate input displayed while
|
||||
// lazy-loading the query input component, to avoid unstable UI.
|
||||
.intermediate-input {
|
||||
height: 100%;
|
||||
font-size: var(--code-font-size);
|
||||
border: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
padding: 0;
|
||||
height: unset;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (--xs-breakpoint-down) {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
.form-control {
|
||||
cursor: text;
|
||||
height: unset;
|
||||
&:focus-within {
|
||||
border: 1px solid var(--input-focus-border-color);
|
||||
box-shadow: var(--search-box-focus-box-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,3 +57,12 @@ export const LazyQueryInput: React.FunctionComponent<LazyQueryInputProps> = ({ .
|
||||
<CodeMirrorQueryInput {...props} />
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
/**
|
||||
* A {@link LazyQueryInput} with a focus ring and other styles from the `.form-control` CSS class.
|
||||
*/
|
||||
export const LazyQueryInputFormControl: React.FunctionComponent<LazyQueryInputProps> = props => (
|
||||
<div className={classNames('form-control', styles.formControl)}>
|
||||
<LazyQueryInput {...props} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,64 +1,5 @@
|
||||
@import 'wildcard/src/global-styles/breakpoints';
|
||||
|
||||
.query-input {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
@media (--xs-breakpoint-down) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&-preview-link {
|
||||
white-space: nowrap;
|
||||
font-size: 0.75rem;
|
||||
background-color: var(--color-bg-2);
|
||||
border: 1px solid var(--input-border-color);
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
&-icon {
|
||||
max-height: 1rem;
|
||||
max-width: 1rem;
|
||||
}
|
||||
|
||||
@media (--xs-breakpoint-down) {
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
border-top: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&-field {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-right: none;
|
||||
background-color: var(--color-bg-1);
|
||||
/* stylelint-disable-next-line declaration-property-unit-allowed-list */
|
||||
height: calc(1rem + 0.5rem * 2 + 1px * 2); /* 1rem for text, 0.5rem vertical padding, 1px border */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 0;
|
||||
min-width: 0;
|
||||
|
||||
&:focus-within {
|
||||
border: 1px solid var(--input-focus-border-color);
|
||||
box-shadow: var(--search-box-focus-box-shadow);
|
||||
}
|
||||
|
||||
@media (--xs-breakpoint-down) {
|
||||
border-right: 1px solid var(--input-border-color);
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trigger-label {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { mdiCheck, mdiHelpCircle, mdiOpenInNew, mdiRadioboxBlank } from '@mdi/js
|
||||
import { VisuallyHidden } from '@reach/visually-hidden'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { LazyQueryInput } from '@sourcegraph/branded'
|
||||
import { LazyQueryInputFormControl } from '@sourcegraph/branded'
|
||||
import type { QueryState } from '@sourcegraph/shared/src/search'
|
||||
import { FilterType, resolveFilter, validateFilter } from '@sourcegraph/shared/src/search/query/filters'
|
||||
import { scanSearchQuery } from '@sourcegraph/shared/src/search/query/scanner'
|
||||
@ -12,7 +12,7 @@ import { useSettingsCascade } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url'
|
||||
import { Button, Card, Checkbox, Code, H3, Icon, Link, Tooltip } from '@sourcegraph/wildcard'
|
||||
|
||||
import { SearchPatternType } from '../../../graphql-operations'
|
||||
import type { SearchPatternType } from '../../../graphql-operations'
|
||||
import { defaultPatternTypeFromSettings } from '../../../util/settings'
|
||||
|
||||
import styles from './FormTriggerArea.module.scss'
|
||||
@ -226,16 +226,12 @@ export const FormTriggerArea: React.FunctionComponent<React.PropsWithChildren<Tr
|
||||
</span>
|
||||
<span className="mt-4">Search query</span>
|
||||
<div>
|
||||
<div className={classNames(styles.queryInput, 'my-2')}>
|
||||
<div className="my-2">
|
||||
<div
|
||||
className={classNames(
|
||||
'form-control',
|
||||
styles.queryInputField,
|
||||
`test-${derivedInputClassName}`
|
||||
)}
|
||||
className={classNames(`test-${derivedInputClassName}`)}
|
||||
data-testid="trigger-query-edit"
|
||||
>
|
||||
<LazyQueryInput
|
||||
<LazyQueryInputFormControl
|
||||
className="test-trigger-input"
|
||||
patternType={defaultPatternType}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
@ -246,21 +242,15 @@ export const FormTriggerArea: React.FunctionComponent<React.PropsWithChildren<Tr
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.queryInputPreviewLink}>
|
||||
<Link
|
||||
to={`/search?${buildSearchURLQuery(queryState.query, defaultPatternType, false)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="test-preview-link"
|
||||
>
|
||||
Preview results{' '}
|
||||
<Icon
|
||||
aria-label="Open in new window"
|
||||
className={classNames('ml-1', styles.queryInputPreviewLinkIcon)}
|
||||
svgPath={mdiOpenInNew}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
to={`/search?${buildSearchURLQuery(queryState.query, defaultPatternType, false)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="test-preview-link d-flex align-items-center flex-gap-1 my-1"
|
||||
>
|
||||
Preview results{' '}
|
||||
<Icon aria-label="Open in new window" className="ml-1" svgPath={mdiOpenInNew} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<ul className={classNames(styles.checklist, 'mb-4')}>
|
||||
|
||||
@ -43,10 +43,6 @@
|
||||
&__query {
|
||||
margin-top: 0.75rem;
|
||||
margin-left: 1.25rem;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--color-bg-1);
|
||||
border: 1px solid var(--border-color-2);
|
||||
}
|
||||
|
||||
&__query-label {
|
||||
|
||||
@ -2,33 +2,33 @@ import React, { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { type Observable, of, throwError, from } from 'rxjs'
|
||||
import { from, of, throwError, type Observable } from 'rxjs'
|
||||
import { catchError, map, startWith, switchMap, tap } from 'rxjs/operators'
|
||||
|
||||
import { SyntaxHighlightedSearchQuery, LazyQueryInput } from '@sourcegraph/branded'
|
||||
import { LazyQueryInputFormControl, SyntaxHighlightedSearchQuery } from '@sourcegraph/branded'
|
||||
import { asError, createAggregateError, isErrorLike } from '@sourcegraph/common'
|
||||
import {
|
||||
SearchPatternType,
|
||||
type Scalars,
|
||||
type SearchContextFields,
|
||||
type SearchContextInput,
|
||||
type SearchContextRepositoryRevisionsInput,
|
||||
SearchPatternType,
|
||||
type SearchContextFields,
|
||||
} from '@sourcegraph/shared/src/graphql-operations'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { QueryState, SearchContextProps } from '@sourcegraph/shared/src/search'
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
|
||||
import {
|
||||
Container,
|
||||
Alert,
|
||||
Button,
|
||||
Code,
|
||||
Container,
|
||||
Form,
|
||||
Input,
|
||||
Link,
|
||||
RadioButton,
|
||||
TextArea,
|
||||
useEventObservable,
|
||||
Alert,
|
||||
Link,
|
||||
Code,
|
||||
Input,
|
||||
Form,
|
||||
} from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
@ -431,7 +431,7 @@ export const SearchContextForm: React.FunctionComponent<React.PropsWithChildren<
|
||||
label={<>Search query</>}
|
||||
/>
|
||||
<div className={styles.searchContextFormQuery} data-testid="search-context-dynamic-query">
|
||||
<LazyQueryInput
|
||||
<LazyQueryInputFormControl
|
||||
patternType={SearchPatternType.regexp}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
caseSensitive={true}
|
||||
|
||||
@ -7,21 +7,6 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.query-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: text;
|
||||
|
||||
:global(.cm-editor) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border: 1px solid var(--input-focus-border-color);
|
||||
box-shadow: var(--search-box-focus-box-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { LazyQueryInput } from '@sourcegraph/branded'
|
||||
import { LazyQueryInputFormControl } from '@sourcegraph/branded'
|
||||
import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing'
|
||||
|
||||
import { SearchPatternType } from '../graphql-operations'
|
||||
@ -10,9 +10,9 @@ import { SavedSearchForm } from './SavedSearchForm'
|
||||
const DEFAULT_PATTERN_TYPE = SearchPatternType.regexp
|
||||
|
||||
describe('SavedSearchForm', () => {
|
||||
test('renders LazyQueryInput with the default patternType', () => {
|
||||
test('renders LazyQueryInputFormControl with the default patternType', () => {
|
||||
vi.mock('@sourcegraph/branded', () => ({
|
||||
LazyQueryInput: vi.fn(() => null),
|
||||
LazyQueryInputFormControl: vi.fn(() => null),
|
||||
}))
|
||||
vi.mock('../util/settings', () => ({
|
||||
defaultPatternTypeFromSettings: () => DEFAULT_PATTERN_TYPE,
|
||||
@ -36,7 +36,7 @@ describe('SavedSearchForm', () => {
|
||||
/>
|
||||
)
|
||||
|
||||
expect(LazyQueryInput).toHaveBeenCalledWith(
|
||||
expect(LazyQueryInputFormControl).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
patternType: DEFAULT_PATTERN_TYPE,
|
||||
}),
|
||||
|
||||
@ -3,27 +3,27 @@ import React, { useEffect, useMemo, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import type { Omit } from 'utility-types'
|
||||
|
||||
import { LazyQueryInput } from '@sourcegraph/branded'
|
||||
import { LazyQueryInputFormControl } from '@sourcegraph/branded'
|
||||
import type { QueryState } from '@sourcegraph/shared/src/search'
|
||||
import { useSettingsCascade } from '@sourcegraph/shared/src/settings/settings'
|
||||
import {
|
||||
Container,
|
||||
PageHeader,
|
||||
ProductStatusBadge,
|
||||
Button,
|
||||
Link,
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
Code,
|
||||
Label,
|
||||
Container,
|
||||
ErrorAlert,
|
||||
Form,
|
||||
Input,
|
||||
Label,
|
||||
Link,
|
||||
PageHeader,
|
||||
ProductStatusBadge,
|
||||
} from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../auth'
|
||||
import { PageTitle } from '../components/PageTitle'
|
||||
import { type Scalars, SearchPatternType } from '../graphql-operations'
|
||||
import { type Scalars, type SearchPatternType } from '../graphql-operations'
|
||||
import type { NamespaceProps } from '../namespaces'
|
||||
import { defaultPatternTypeFromSettings } from '../util/settings'
|
||||
|
||||
@ -60,7 +60,6 @@ export const SavedSearchForm: React.FunctionComponent<React.PropsWithChildren<Sa
|
||||
|
||||
/**
|
||||
* Returns an input change handler that updates the SavedQueryFields in the component's state
|
||||
*
|
||||
* @param key The key of saved query fields that a change of this input should update
|
||||
*/
|
||||
const createInputChangeHandler =
|
||||
@ -124,9 +123,7 @@ export const SavedSearchForm: React.FunctionComponent<React.PropsWithChildren<Sa
|
||||
/>
|
||||
<Label className={classNames('w-100 form-group', styles.label)}>
|
||||
<div className="mb-2">Query</div>
|
||||
|
||||
<LazyQueryInput
|
||||
className={classNames('form-control', styles.queryInput)}
|
||||
<LazyQueryInputFormControl
|
||||
patternType={defaultPatternType}
|
||||
isSourcegraphDotCom={props.isSourcegraphDotCom}
|
||||
caseSensitive={false}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user