Skip to content

Commit

Permalink
fix unified search for long field inputs (#166024)
Browse files Browse the repository at this point in the history
## Summary

Fixes #166019


![long-field-name-input](https://github.com/elastic/kibana/assets/1280964/be343d1b-e639-4b04-9742-0bcf1e9f032d)


### Checklist

Delete any items that are not applicable to this PR.

- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: nickofthyme <nicholas.partridge@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
  • Loading branch information
4 people authored Sep 11, 2023
1 parent 011a9d1 commit c6770af
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface GenericComboBoxProps<T> {
searchValue: string,
OPTION_CONTENT_CLASSNAME: string
) => React.ReactNode;
inputRef?: ((instance: HTMLInputElement | null) => void) | undefined;
[propName: string]: any;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,8 @@ const COMBOBOX_PADDINGS = 10;
const DEFAULT_FONT = '14px Inter';

class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
comboBoxRef: React.RefObject<HTMLInputElement>;

constructor(props: PhraseValueInputProps) {
super(props);
this.comboBoxRef = React.createRef();
}
comboBoxWrapperRef = React.createRef<HTMLDivElement>();
inputRef: HTMLInputElement | null = null;

public render() {
return (
Expand Down Expand Up @@ -69,8 +65,11 @@ class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
const valueAsStr = String(value);
const options = value ? uniq([valueAsStr, ...suggestions]) : suggestions;
return (
<div ref={this.comboBoxRef}>
<div ref={this.comboBoxWrapperRef}>
<StringComboBox
inputRef={(ref) => {
this.inputRef = ref;
}}
isDisabled={this.props.disabled}
fullWidth={fullWidth}
compressed={this.props.compressed}
Expand All @@ -85,7 +84,13 @@ class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
options={options}
getLabel={(option) => option}
selectedOptions={value ? [valueAsStr] : []}
onChange={([newValue = '']) => onChange(newValue)}
onChange={([newValue = '']) => {
onChange(newValue);
setTimeout(() => {
// Note: requires a tick skip to correctly blur element focus
this.inputRef?.blur();
});
}}
onSearchChange={this.onSearchChange}
singleSelection={{ asPlainText: true }}
onCreateOption={onChange}
Expand All @@ -98,7 +103,7 @@ class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
defaultFont={DEFAULT_FONT}
comboboxPaddings={COMBOBOX_PADDINGS}
comboBoxRef={this.comboBoxRef}
comboBoxWrapperRef={this.comboBoxWrapperRef}
label={option.label}
search={searchValue}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,15 @@ const COMBOBOX_PADDINGS = 20;
const DEFAULT_FONT = '14px Inter';

class PhrasesValuesInputUI extends PhraseSuggestorUI<PhrasesValuesInputProps> {
comboBoxRef: React.RefObject<HTMLInputElement>;

constructor(props: PhrasesValuesInputProps) {
super(props);
this.comboBoxRef = React.createRef();
}
comboBoxWrapperRef = React.createRef<HTMLDivElement>();

public render() {
const { suggestions } = this.state;
const { values, intl, onChange, fullWidth, onParamsUpdate, compressed, disabled } = this.props;
const options = values ? uniq([...values, ...suggestions]) : suggestions;

return (
<div ref={this.comboBoxRef}>
<div ref={this.comboBoxWrapperRef}>
<StringComboBox
fullWidth={fullWidth}
compressed={compressed}
Expand Down Expand Up @@ -79,7 +74,7 @@ class PhrasesValuesInputUI extends PhraseSuggestorUI<PhrasesValuesInputProps> {
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
defaultFont={DEFAULT_FONT}
comboboxPaddings={COMBOBOX_PADDINGS}
comboBoxRef={this.comboBoxRef}
comboBoxWrapperRef={this.comboBoxWrapperRef}
label={option.label}
search={searchValue}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@
* Side Public License, v 1.
*/

import React from 'react';
import React, { ComponentProps } from 'react';
import { mount } from 'enzyme';
import { TruncatedLabel } from './truncated_label';

describe('truncated_label', () => {
const defaultProps = {
const defaultProps: ComponentProps<typeof TruncatedLabel> = {
defaultFont: '14px Inter',
// jest-canvas-mock mocks measureText as the number of string characters, thats why the width is so low
width: 30,
defaultComboboxWidth: 130,
comboboxPaddings: 100,
comboBoxRef: React.createRef<HTMLInputElement>(),
comboBoxWrapperRef: React.createRef<HTMLDivElement>(),
search: '',
label: 'example_field',
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { throttle } from 'lodash';
interface TruncatedLabelProps {
label: string;
search: string;
comboBoxRef: RefObject<HTMLInputElement>;
comboBoxWrapperRef: RefObject<HTMLDivElement | null>;
defaultFont: string;
defaultComboboxWidth: number;
comboboxPaddings: number;
Expand Down Expand Up @@ -56,7 +56,7 @@ const truncateLabel = (

export const TruncatedLabel = React.memo(function TruncatedLabel({
label,
comboBoxRef,
comboBoxWrapperRef,
search,
defaultFont,
defaultComboboxWidth,
Expand All @@ -69,15 +69,14 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({
width: defaultComboboxWidth - comboboxPaddings,
font: defaultFont,
});

const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => {
if (comboBoxRef.current) {
if (comboBoxWrapperRef.current) {
const current = {
...labelProps,
width: comboBoxRef.current?.clientWidth - comboboxPaddings,
width: comboBoxWrapperRef.current.clientWidth - comboboxPaddings,
};
if (shouldRecomputeAll) {
current.font = window.getComputedStyle(comboBoxRef.current).font;
current.font = window.getComputedStyle(comboBoxWrapperRef.current).font;
}
setLabelProps(current);
}
Expand All @@ -88,7 +87,7 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({
}, 50);

useEffectOnce(() => {
if (comboBoxRef.current) {
if (comboBoxWrapperRef.current) {
handleResize(undefined, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
const { disabled, suggestionsAbstraction } = useContext(FiltersBuilderContextType);
const fields = dataView ? getFilterableFields(dataView) : [];
const id = useGeneratedHtmlId({ prefix: 'fieldInput' });
const comboBoxRef = useRef<HTMLInputElement>(null);
const comboBoxWrapperRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);

const onFieldChange = useCallback(
([selectedField]: DataViewField[]) => {
Expand Down Expand Up @@ -77,12 +78,25 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
({ label }) => fields[optionFields.findIndex((optionField) => optionField.label === label)]
);
onFieldChange(newValues);

setTimeout(() => {
// Note: requires a tick skip to correctly blur element focus
inputRef?.current?.blur();
});
};

const handleFocus: React.FocusEventHandler<HTMLDivElement> = () => {
// Force focus on input due to https://github.com/elastic/eui/issues/7170
inputRef?.current?.focus();
};

return (
<div ref={comboBoxRef}>
<div ref={comboBoxWrapperRef}>
<EuiComboBox
id={id}
inputRef={(ref) => {
inputRef.current = ref;
}}
options={euiOptions}
selectedOptions={selectedEuiOptions}
onChange={onComboBoxChange}
Expand All @@ -94,6 +108,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
isClearable={false}
compressed
fullWidth
onFocus={handleFocus}
data-test-subj="filterFieldSuggestionList"
renderOption={(option, searchValue) => (
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
Expand All @@ -105,7 +120,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
defaultFont={DEFAULT_FONT}
comboboxPaddings={COMBOBOX_PADDINGS}
comboBoxRef={comboBoxRef}
comboBoxWrapperRef={comboBoxWrapperRef}
label={option.label}
search={searchValue}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export const cursorOrCss = css`

export const fieldAndParamCss = (euiTheme: EuiThemeComputed) => css`
min-width: calc(${euiTheme.size.xl} * 5);
flex-grow: 1;
.euiFormRow {
max-width: 800px;
}
&:focus-within {
flex-grow: 4;
}
`;

export const operationCss = (euiTheme: EuiThemeComputed) => css`
Expand Down

0 comments on commit c6770af

Please sign in to comment.