[UnifiedFieldList] Show drag handle on item hover (#173673)

- Resolves https://github.com/elastic/kibana/issues/168856

## Summary

As per comment in
https://github.com/elastic/kibana/pull/171572#issuecomment-1841587066

> As a simple way to mitigate this issue, what if we changed it so that
on hover/focus of a field list item we fade-out the token and fade-in
the drag handle to replace the token (in the same position)? In doing
so, we could also keep the old translate x-axis transition (where the
field list item slides a few pixels to the right) to emphasize that it's
draggable. That little bit of extra movement might be good, if the
appearance of the drag handle is no longer pushing the text (given the
above suggestion).

![Dec-19-2023
18-13-21](e02cb7d6-ce1a-4507-a8a6-3004f89d7225)


### Checklist

- [x] [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
- [x] 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))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Julia Rechkunova 2024-01-03 16:20:06 +01:00 committed by GitHub
parent 085da724c9
commit 76e812133a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 61 deletions

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UnifiedFieldList <FieldItemButton /> renders properly 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly 1`] = `
<FieldButton
buttonProps={
Object {
@ -10,10 +10,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly 1`] = `
className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists"
dataTestSubj="field-bytes-showDetails"
fieldIcon={
<WrappedFieldIcon
scripted={false}
type="number"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={false}
type="number"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -31,7 +39,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly 1`] = `
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly for Records (Lens field) 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly for Records (Lens field) 1`] = `
<FieldButton
buttonProps={
Object {
@ -41,10 +49,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for Records (Lens
className="unifiedFieldListItemButton unifiedFieldListItemButton--document unifiedFieldListItemButton--exists"
dataTestSubj="field-___records___-showDetails"
fieldIcon={
<WrappedFieldIcon
scripted={false}
type="document"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={false}
type="document"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -62,7 +78,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for Records (Lens
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly for search with spaces 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly for search with spaces 1`] = `
<FieldButton
buttonProps={
Object {
@ -72,10 +88,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for search with s
className="unifiedFieldListItemButton unifiedFieldListItemButton--date unifiedFieldListItemButton--exists"
dataTestSubj="field-script date-showDetails"
fieldIcon={
<WrappedFieldIcon
scripted={true}
type="date"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={true}
type="date"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -92,7 +116,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for search with s
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly for text-based column field 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly for text-based column field 1`] = `
<FieldButton
buttonProps={
Object {
@ -102,9 +126,17 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for text-based co
className="unifiedFieldListItemButton unifiedFieldListItemButton--string unifiedFieldListItemButton--exists"
dataTestSubj="field-agent-showDetails"
fieldIcon={
<WrappedFieldIcon
type="string"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
type="string"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -121,7 +153,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for text-based co
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly for wildcard search 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly for wildcard search 1`] = `
<FieldButton
buttonProps={
Object {
@ -131,10 +163,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for wildcard sear
className="unifiedFieldListItemButton unifiedFieldListItemButton--date unifiedFieldListItemButton--exists"
dataTestSubj="field-script date-showDetails"
fieldIcon={
<WrappedFieldIcon
scripted={true}
type="date"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={true}
type="date"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -151,7 +191,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly for wildcard sear
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly when a conflict field 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly when a conflict field 1`] = `
<FieldButton
buttonProps={
Object {
@ -161,10 +201,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly when a conflict f
className="unifiedFieldListItemButton unifiedFieldListItemButton--conflict unifiedFieldListItemButton--exists"
dataTestSubj="field-custom_user_field-showDetails"
fieldIcon={
<WrappedFieldIcon
scripted={false}
type="conflict"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={false}
type="conflict"
/>
</div>
</div>
}
fieldInfoIcon={
<FieldConflictInfoIcon
@ -196,7 +244,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly when a conflict f
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly when empty 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly when empty 1`] = `
<FieldButton
buttonProps={
Object {
@ -206,10 +254,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly when empty 1`] =
className="unifiedFieldListItemButton unifiedFieldListItemButton--date unifiedFieldListItemButton--missing"
dataTestSubj="field-script date-showDetails"
fieldIcon={
<WrappedFieldIcon
scripted={true}
type="date"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={true}
type="date"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -227,25 +283,36 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly when empty 1`] =
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly with a drag handle 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly with a drag icon 1`] = `
<FieldButton
buttonProps={
Object {
"aria-label": "Preview bytes: number",
}
}
className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists unifiedFieldListItemButton--withDragHandle custom"
className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists unifiedFieldListItemButton--withDragIcon custom"
dataTestSubj="test-subj"
dragHandle={
<span>
dragHandle
</span>
}
fieldIcon={
<WrappedFieldIcon
scripted={false}
type="number"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={false}
type="number"
/>
</div>
<div
className="unifiedFieldListItemButton__fieldIconDrag"
>
<EuiIcon
size="m"
type="grabOmnidirectional"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -262,7 +329,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly with a drag handl
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly with an action when deselected 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly with an action when deselected 1`] = `
<FieldButton
buttonProps={
Object {
@ -289,10 +356,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly with an action wh
</EuiToolTip>
}
fieldIcon={
<WrappedFieldIcon
scripted={false}
type="number"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={false}
type="number"
/>
</div>
</div>
}
fieldName={
<EuiHighlight
@ -309,7 +384,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly with an action wh
/>
`;
exports[`UnifiedFieldList <FieldItemButton /> renders properly with an action when selected 1`] = `
exports[`UnifiedFieldList FieldItemButton renders properly with an action when selected 1`] = `
<FieldButton
buttonProps={
Object {
@ -336,10 +411,18 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly with an action wh
</EuiToolTip>
}
fieldIcon={
<WrappedFieldIcon
scripted={false}
type="number"
/>
<div
className="unifiedFieldListItemButton__fieldIconContainer"
>
<div
className="unifiedFieldListItemButton__fieldIcon"
>
<WrappedFieldIcon
scripted={false}
type="number"
/>
</div>
</div>
}
fieldName={
<EuiHighlight

View file

@ -70,3 +70,38 @@
background: lightOrDarkTheme(transparentize($euiColorMediumShade, .9), $euiColorEmptyShade);
color: $euiColorDarkShade;
}
.unifiedFieldListItemButton__fieldIconContainer {
position: relative;
}
.unifiedFieldListItemButton__fieldIcon {
transition: opacity $euiAnimSpeedNormal ease-in-out;
}
.unifiedFieldListItemButton__fieldIconDrag {
visibility: hidden;
position: absolute;
top: 0;
left: 0;
transition: opacity $euiAnimSpeedNormal ease-in-out;
}
// A drag handle will appear only on item hover/focus
.unifiedFieldListItemButton--withDragIcon {
.unifiedFieldListItemButton__fieldIconDrag {
visibility: visible;
opacity: 0;
}
&:hover,
&[class*='-isActive'],
.domDragDrop__keyboardHandler:focus + & {
.unifiedFieldListItemButton__fieldIcon {
opacity: 0;
}
.unifiedFieldListItemButton__fieldIconDrag {
opacity: 1;
}
}
}

View file

@ -17,7 +17,7 @@ const bytesField = dataView.getFieldByName('bytes')!;
const scriptedField = dataView.getFieldByName('script date')!;
const conflictField = dataView.getFieldByName('custom_user_field')!;
describe('UnifiedFieldList <FieldItemButton />', () => {
describe('UnifiedFieldList FieldItemButton', () => {
test('renders properly', () => {
const component = shallow(
<FieldItemButton
@ -115,13 +115,13 @@ describe('UnifiedFieldList <FieldItemButton />', () => {
expect(component).toMatchSnapshot();
});
test('renders properly with a drag handle', () => {
test('renders properly with a drag icon', () => {
const component = shallow(
<FieldItemButton
size="xs"
className="custom"
dataTestSubj="test-subj"
dragHandle={<span>dragHandle</span>}
withDragIcon={true}
field={bytesField}
fieldSearchHighlight={undefined}
isEmpty={false}

View file

@ -16,6 +16,8 @@ import { FieldIcon, getFieldIconProps, getFieldSearchMatchingHighlight } from '@
import { type FieldListItem, type GetCustomFieldType } from '../../types';
import './field_item_button.scss';
const DRAG_ICON = <EuiIcon type="grabOmnidirectional" size="m" />;
/**
* Props of FieldItemButton component
*/
@ -28,7 +30,7 @@ export interface FieldItemButtonProps<T extends FieldListItem> {
infoIcon?: FieldButtonProps['fieldInfoIcon'];
className?: FieldButtonProps['className'];
flush?: FieldButtonProps['flush'];
dragHandle?: FieldButtonProps['dragHandle'];
withDragIcon?: boolean;
getCustomFieldType?: GetCustomFieldType<T>;
dataTestSubj?: string;
size?: FieldButtonProps['size'];
@ -52,6 +54,7 @@ export interface FieldItemButtonProps<T extends FieldListItem> {
* @param getCustomFieldType
* @param dataTestSubj
* @param size
* @param withDragIcon
* @param onClick
* @param shouldAlwaysShowAction
* @param buttonAddFieldToWorkspaceProps
@ -73,6 +76,7 @@ export function FieldItemButton<T extends FieldListItem = DataViewField>({
getCustomFieldType,
dataTestSubj,
size,
withDragIcon,
onClick,
shouldAlwaysShowAction,
buttonAddFieldToWorkspaceProps,
@ -104,7 +108,7 @@ export function FieldItemButton<T extends FieldListItem = DataViewField>({
[`unifiedFieldListItemButton--${type}`]: type,
[`unifiedFieldListItemButton--exists`]: !isEmpty,
[`unifiedFieldListItemButton--missing`]: isEmpty,
[`unifiedFieldListItemButton--withDragHandle`]: Boolean(otherProps.dragHandle),
[`unifiedFieldListItemButton--withDragIcon`]: Boolean(withDragIcon),
},
className
);
@ -196,7 +200,16 @@ export function FieldItemButton<T extends FieldListItem = DataViewField>({
},
}),
}}
fieldIcon={<FieldIcon {...iconProps} />}
fieldIcon={
<div className="unifiedFieldListItemButton__fieldIconContainer">
<div className="unifiedFieldListItemButton__fieldIcon">
<FieldIcon {...iconProps} />
</div>
{withDragIcon && (
<div className="unifiedFieldListItemButton__fieldIconDrag">{DRAG_ICON}</div>
)}
</div>
}
fieldName={
<EuiHighlight
search={getFieldSearchMatchingHighlight(displayName, fieldSearchHighlight)}

View file

@ -349,6 +349,7 @@ function UnifiedFieldListItemComponent({
fieldSearchHighlight={highlight}
isEmpty={isEmpty}
isActive={infoIsOpen}
withDragIcon={!isDragDisabled}
flush={alwaysShowActionButton ? 'both' : undefined}
shouldAlwaysShowAction={alwaysShowActionButton}
onClick={field.type !== '_source' ? togglePopover : undefined}

View file

@ -186,6 +186,7 @@ export function InnerFieldItem(props: FieldItemProps) {
isSelected: false, // multiple selections are allowed
isEmpty: !exists,
isActive: infoIsOpen,
withDragIcon: true,
fieldSearchHighlight: highlight,
onClick: togglePopover,
buttonAddFieldToWorkspaceProps,