mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
## Summary Fixes 3/5 of https://github.com/elastic/kibana/issues/168753 Doesn't set up multilines. Doesn't remove auto-expanding logic. Middle truncates. (Unified Search) Field selector <img width="984" alt="Screenshot 2023-11-13 at 11 30 20" src="9acb6462
-3205-4e5c-81bd-c3ae10c8323b"> (Unified Search) Value selector: <img width="972" alt="Screenshot 2023-11-13 at 11 30 30" src="e58b09de
-d582-431f-bbd6-97b7c5bd38de"> (Lens) Field picker within layer config: <img width="346" alt="Screenshot 2023-11-09 at 14 44 58" src="4ecb0ea5
-bb01-49e3-a54f-4c8c5884c418"> Also fixes tiny stylistic issue for dataview picker label cut on the bottom: <img width="368" alt="Screenshot 2023-11-09 at 15 06 38" src="b9ae6956
-c1ef-481e-905d-71ffe5e5545a"> <img width="386" alt="Screenshot 2023-11-09 at 15 07 08" src="5d49ed7a
-e8f2-40c1-ac53-a3580b82740e">
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
/*
|
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
* or more contributor license agreements. Licensed under the Elastic License
|
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
* Side Public License, v 1.
|
|
*/
|
|
|
|
import './field_picker.scss';
|
|
import React from 'react';
|
|
import { i18n } from '@kbn/i18n';
|
|
import { EuiComboBox, EuiComboBoxProps } from '@elastic/eui';
|
|
import { FieldIcon } from '@kbn/field-utils/src/components/field_icon';
|
|
import classNames from 'classnames';
|
|
import type { FieldOptionValue, FieldOption } from './types';
|
|
|
|
export interface FieldPickerProps<T extends FieldOptionValue>
|
|
extends EuiComboBoxProps<FieldOption<T>['value']> {
|
|
options: Array<FieldOption<T>>;
|
|
selectedField?: string;
|
|
onChoose: (choice: T | undefined) => void;
|
|
onDelete?: () => void;
|
|
fieldIsInvalid: boolean;
|
|
'data-test-subj'?: string;
|
|
}
|
|
|
|
const MIDDLE_TRUNCATION_PROPS = { truncation: 'middle' as const };
|
|
const SINGLE_SELECTION_AS_TEXT_PROPS = { asPlainText: true };
|
|
|
|
export function FieldPicker<T extends FieldOptionValue = FieldOptionValue>({
|
|
selectedOptions,
|
|
options,
|
|
onChoose,
|
|
onDelete,
|
|
fieldIsInvalid,
|
|
['data-test-subj']: dataTestSub,
|
|
...rest
|
|
}: FieldPickerProps<T>) {
|
|
let theLongestLabel = '';
|
|
const styledOptions = options?.map(({ compatible, exists, ...otherAttr }) => {
|
|
if (otherAttr.options) {
|
|
return {
|
|
...otherAttr,
|
|
options: otherAttr.options.map(({ exists: fieldOptionExists, ...fieldOption }) => {
|
|
if (fieldOption.label.length > theLongestLabel.length) {
|
|
theLongestLabel = fieldOption.label;
|
|
}
|
|
return {
|
|
...fieldOption,
|
|
prepend: fieldOption.value.dataType ? (
|
|
<FieldIcon
|
|
type={fieldOption.value.dataType}
|
|
fill="none"
|
|
className="eui-alignMiddle"
|
|
/>
|
|
) : null,
|
|
className: classNames({
|
|
'lnFieldPicker__option--incompatible': !fieldOption.compatible,
|
|
'lnFieldPicker__option--nonExistant': !fieldOptionExists,
|
|
}),
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
return {
|
|
...otherAttr,
|
|
compatible,
|
|
prepend: otherAttr.value.dataType ? (
|
|
<FieldIcon type={otherAttr.value.dataType} fill="none" className="eui-alignMiddle" />
|
|
) : null,
|
|
className: classNames({
|
|
'lnFieldPicker__option--incompatible': !compatible,
|
|
'lnFieldPicker__option--nonExistant': !exists,
|
|
}),
|
|
};
|
|
});
|
|
|
|
const panelMinWidth = getPanelMinWidth(theLongestLabel.length);
|
|
return (
|
|
<EuiComboBox
|
|
fullWidth
|
|
compressed
|
|
isClearable={false}
|
|
data-test-subj={dataTestSub ?? 'indexPattern-dimension-field'}
|
|
placeholder={i18n.translate('visualizationUiComponents.fieldPicker.fieldPlaceholder', {
|
|
defaultMessage: 'Select a field',
|
|
})}
|
|
options={styledOptions}
|
|
isInvalid={fieldIsInvalid}
|
|
selectedOptions={selectedOptions}
|
|
singleSelection={SINGLE_SELECTION_AS_TEXT_PROPS}
|
|
truncationProps={MIDDLE_TRUNCATION_PROPS}
|
|
inputPopoverProps={{ panelMinWidth }}
|
|
onChange={(choices) => {
|
|
if (choices.length === 0) {
|
|
onDelete?.();
|
|
return;
|
|
}
|
|
onChoose(choices[0].value);
|
|
}}
|
|
{...rest}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const MINIMUM_POPOVER_WIDTH = 300;
|
|
const MINIMUM_POPOVER_WIDTH_CHAR_COUNT = 28;
|
|
const AVERAGE_CHAR_WIDTH = 7;
|
|
const MAXIMUM_POPOVER_WIDTH_CHAR_COUNT = 60;
|
|
const MAXIMUM_POPOVER_WIDTH = 550; // fitting 60 characters
|
|
|
|
function getPanelMinWidth(labelLength: number) {
|
|
if (labelLength > MAXIMUM_POPOVER_WIDTH_CHAR_COUNT) {
|
|
return MAXIMUM_POPOVER_WIDTH;
|
|
}
|
|
if (labelLength > MINIMUM_POPOVER_WIDTH_CHAR_COUNT) {
|
|
const overflownChars = labelLength - MINIMUM_POPOVER_WIDTH_CHAR_COUNT;
|
|
return MINIMUM_POPOVER_WIDTH + overflownChars * AVERAGE_CHAR_WIDTH;
|
|
}
|
|
return MINIMUM_POPOVER_WIDTH;
|
|
}
|