mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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).

### 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:
parent
085da724c9
commit
76e812133a
6 changed files with 194 additions and 61 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue