-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Controls] [PresentationUtil] QoL improvements to control creation form #162067
Changes from all commits
8ac6ad5
a5f8827
2e07d31
332e87d
8ff957e
6a98581
3e9f301
bb9c0c4
7590228
52d4e26
650ba4c
f49e55e
039c98f
5711793
ee6da5c
cdbef0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ | |
|
||
import classNames from 'classnames'; | ||
import { sortBy, uniq } from 'lodash'; | ||
import React, { useEffect, useMemo, useState } from 'react'; | ||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | ||
|
||
import { i18n } from '@kbn/i18n'; | ||
import { FieldIcon } from '@kbn/react-field'; | ||
|
@@ -39,8 +39,12 @@ export const FieldPicker = ({ | |
filterPredicate, | ||
selectedFieldName, | ||
selectableProps, | ||
...other | ||
}: FieldPickerProps) => { | ||
const initialSelection = useRef(selectedFieldName); | ||
|
||
const [typesFilter, setTypesFilter] = useState<string[]>([]); | ||
const [searchRef, setSearchRef] = useState<HTMLInputElement | null>(null); | ||
const [fieldSelectableOptions, setFieldSelectableOptions] = useState<EuiSelectableOption[]>([]); | ||
|
||
const availableFields = useMemo( | ||
|
@@ -50,7 +54,7 @@ export const FieldPicker = ({ | |
.filter((f) => typesFilter.length === 0 || typesFilter.includes(f.type as string)) | ||
.filter((f) => (filterPredicate ? filterPredicate(f) : true)), | ||
['name'] | ||
), | ||
).sort((f) => (f.name === initialSelection.current ? -1 : 1)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This brings the selected field, if provided, to the top of the list on load. |
||
[dataView, filterPredicate, typesFilter] | ||
); | ||
|
||
|
@@ -60,9 +64,8 @@ export const FieldPicker = ({ | |
return { | ||
key: field.name, | ||
label: field.displayName ?? field.name, | ||
className: classNames('presFieldPicker__fieldButton', { | ||
presFieldPickerFieldButtonActive: field.name === selectedFieldName, | ||
}), | ||
className: 'presFieldPicker__fieldButton', | ||
checked: field.name === selectedFieldName ? 'on' : undefined, | ||
'data-test-subj': `field-picker-select-${field.name}`, | ||
prepend: ( | ||
<FieldIcon | ||
|
@@ -89,9 +92,14 @@ export const FieldPicker = ({ | |
[dataView, filterPredicate] | ||
); | ||
|
||
const setFocusToSearch = useCallback(() => { | ||
searchRef?.focus(); | ||
}, [searchRef]); | ||
|
||
const fieldTypeFilter = ( | ||
<EuiFormRow fullWidth={true}> | ||
<FieldTypeFilter | ||
setFocusToSearch={setFocusToSearch} | ||
onFieldTypesChange={(types) => setTypesFilter(types)} | ||
fieldTypesValue={typesFilter} | ||
availableFieldTypes={uniqueTypes} | ||
|
@@ -102,6 +110,7 @@ export const FieldPicker = ({ | |
|
||
return ( | ||
<EuiSelectable | ||
{...other} | ||
{...selectableProps} | ||
className={classNames('fieldPickerSelectable', { | ||
fieldPickerSelectableLoading: selectableProps?.isLoading, | ||
|
@@ -126,6 +135,7 @@ export const FieldPicker = ({ | |
defaultMessage: 'Search field names', | ||
}), | ||
disabled: Boolean(selectableProps?.isLoading), | ||
inputRef: setSearchRef, | ||
}} | ||
listProps={{ | ||
isVirtualized: true, | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,35 +7,33 @@ | |
*/ | ||
|
||
import React, { useState } from 'react'; | ||
import { i18n } from '@kbn/i18n'; | ||
|
||
import { | ||
EuiFilterGroup, | ||
EuiFlexGroup, | ||
EuiFlexItem, | ||
EuiPopover, | ||
EuiInputPopover, | ||
EuiContextMenuPanel, | ||
EuiContextMenuItem, | ||
EuiOutsideClickDetector, | ||
EuiFilterButton, | ||
EuiPopoverTitle, | ||
EuiFilterButtonProps, | ||
} from '@elastic/eui'; | ||
import { FieldIcon } from '@kbn/react-field'; | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
|
||
import './field_type_filter.scss'; | ||
|
||
export interface Props { | ||
onFieldTypesChange: (value: string[]) => void; | ||
fieldTypesValue: string[]; | ||
availableFieldTypes: string[]; | ||
buttonProps?: Partial<EuiFilterButtonProps>; | ||
setFocusToSearch: () => void; | ||
availableFieldTypes: string[]; | ||
fieldTypesValue: string[]; | ||
} | ||
|
||
export function FieldTypeFilter({ | ||
availableFieldTypes, | ||
onFieldTypesChange, | ||
setFocusToSearch, | ||
fieldTypesValue, | ||
availableFieldTypes, | ||
buttonProps, | ||
}: Props) { | ||
const [isPopoverOpen, setPopoverOpen] = useState(false); | ||
|
@@ -63,48 +61,45 @@ export function FieldTypeFilter({ | |
); | ||
|
||
return ( | ||
<EuiOutsideClickDetector onOutsideClick={() => {}} isDisabled={!isPopoverOpen}> | ||
<EuiFilterGroup fullWidth> | ||
<EuiPopover | ||
panelClassName="presFilterByType__panel" | ||
panelPaddingSize="none" | ||
display="block" | ||
isOpen={isPopoverOpen} | ||
closePopover={() => { | ||
setPopoverOpen(false); | ||
}} | ||
button={buttonContent} | ||
> | ||
<EuiPopoverTitle paddingSize="s"> | ||
{i18n.translate('presentationUtil.fieldSearch.filterByTypeLabel', { | ||
defaultMessage: 'Filter by type', | ||
})} | ||
</EuiPopoverTitle> | ||
<EuiContextMenuPanel | ||
items={(availableFieldTypes as string[]).map((type) => ( | ||
<EuiContextMenuItem | ||
key={type} | ||
icon={fieldTypesValue.includes(type) ? 'check' : 'empty'} | ||
data-test-subj={`typeFilter-${type}`} | ||
onClick={() => { | ||
if (fieldTypesValue.includes(type)) { | ||
onFieldTypesChange(fieldTypesValue.filter((f) => f !== type)); | ||
} else { | ||
onFieldTypesChange([...fieldTypesValue, type]); | ||
} | ||
}} | ||
> | ||
<EuiFlexGroup gutterSize="xs" responsive={false}> | ||
<EuiFlexItem grow={false}> | ||
<FieldIcon type={type} label={type} /> | ||
</EuiFlexItem> | ||
<EuiFlexItem>{type}</EuiFlexItem> | ||
</EuiFlexGroup> | ||
</EuiContextMenuItem> | ||
))} | ||
/> | ||
</EuiPopover> | ||
</EuiFilterGroup> | ||
</EuiOutsideClickDetector> | ||
<EuiFilterGroup fullWidth> | ||
<EuiInputPopover | ||
panelPaddingSize="none" | ||
display="block" | ||
isOpen={isPopoverOpen} | ||
closePopover={() => { | ||
setPopoverOpen(false); | ||
}} | ||
fullWidth | ||
input={buttonContent} | ||
focusTrapProps={{ | ||
returnFocus: false, // we will be manually returning the focus to the search | ||
onDeactivation: setFocusToSearch, | ||
}} | ||
Comment on lines
+74
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the important change in this file - it ensures that, as described in elastic/eui#6627 (comment), the focus returns to the search field when the field type filter is closed (either via |
||
> | ||
<EuiContextMenuPanel | ||
items={(availableFieldTypes as string[]).map((type) => ( | ||
<EuiContextMenuItem | ||
key={type} | ||
icon={fieldTypesValue.includes(type) ? 'check' : 'empty'} | ||
data-test-subj={`typeFilter-${type}`} | ||
onClick={() => { | ||
if (fieldTypesValue.includes(type)) { | ||
onFieldTypesChange(fieldTypesValue.filter((f) => f !== type)); | ||
} else { | ||
onFieldTypesChange([...fieldTypesValue, type]); | ||
} | ||
}} | ||
> | ||
<EuiFlexGroup gutterSize="xs" responsive={false}> | ||
<EuiFlexItem grow={false}> | ||
<FieldIcon type={type} label={type} /> | ||
</EuiFlexItem> | ||
<EuiFlexItem>{type}</EuiFlexItem> | ||
</EuiFlexGroup> | ||
</EuiContextMenuItem> | ||
))} | ||
/> | ||
</EuiInputPopover> | ||
</EuiFilterGroup> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched this to a
div
so that it can receive theid
passed down from theEuiFormRow
component - not sure why, but the React fragment ignored it 🤷