Skip to content

Commit

Permalink
[Controls] [PresentationUtil] QoL improvements to control creation fo…
Browse files Browse the repository at this point in the history
…rm (#162067)

Closes #162697

## Summary

This PR adds a few tiny UI improvements to the control creation
elements, including...
  
**Data view picker:**
- Made the `Data view` form title respond to focus as expected
  | <div align="center">Before</div> | <div align="center">After</div> |
  |--------|--------|
| ![Jul-28-2023
15-55-13](https://github.com/elastic/kibana/assets/8698078/c287978d-a54a-4809-a806-5a2caa41cf5d)
| ![Jul-28-2023
15-56-24](https://github.com/elastic/kibana/assets/8698078/8f403c2d-80a5-4fc1-989a-1ecceb056fc9)
|

- Switched to use `EuiInputPopover` rather than `EuiPopover`
- Removed the redundant popover title

  | <div align="center">Before</div> | <div align="center">After</div> |
  |--------|--------|
|
![image](https://github.com/elastic/kibana/assets/8698078/013fc848-3a9a-4280-9b37-6c1f025f3597)
| ![Screenshot 2023-07-28 at 4 16 18
PM](https://github.com/elastic/kibana/assets/8698078/22a2de30-cae1-49d4-9c33-d1537488d08d)
|

**Field picker:**
- Made the `Field` form row title respond to focus as expected for all
of the inner form elements
  | <div align="center">Before</div> | <div align="center">After</div> |
  |--------|--------|
| ![Jul-28-2023
16-06-01](https://github.com/elastic/kibana/assets/8698078/7dd845bc-0476-4b2a-b9b5-efce3c2e2844)
| ![Jul-28-2023
16-07-00](https://github.com/elastic/kibana/assets/8698078/222a9199-e5c2-4180-9501-e31588020855)
|

- Switched the `FieldTypeFilter` to use `EuiInputPopover` rather than
`EuiPopover`
- Removed the redundant title from the `FieldTypeFilter` popover
  | <div align="center">Before</div> | <div align="center">After</div> |
  |--------|--------|
|
![image](https://github.com/elastic/kibana/assets/8698078/007c61db-989b-4615-a36f-5f6307f04aaf)
|
![image](https://github.com/elastic/kibana/assets/8698078/ed7aea0c-d852-4f1c-ae03-14933fa2888a)
|

- Made changes described in
elastic/eui#6627 (comment) so
that, when the field type filter is closed (either via `Esc` or through
the natural tab order), the focus returns to the search field
  | <div align="center">Before</div> | <div align="center">After</div> |
  |--------|--------|
| ![Jul-28-2023
16-12-58](https://github.com/elastic/kibana/assets/8698078/aea49501-1f61-4ae8-bc90-1bacbbc232e7)
| ![Jul-28-2023
16-13-54](https://github.com/elastic/kibana/assets/8698078/8068b090-9cca-427f-bc36-2b9e6b2324f1)
|

- If provided, the initial selected field is now brought to the top of
the list

  | <div align="center">Before</div> | <div align="center">After</div> |
  |--------|--------|
|
![image](https://github.com/elastic/kibana/assets/8698078/2bdad643-d184-4c80-b940-5a73820dc8a5)
|
![image](https://github.com/elastic/kibana/assets/8698078/cda382e2-0e15-48c0-bdbf-c530a77570b8)
|

**Controls display settings:**

- Surrounded the `Minimum width` row with a `div` so that it can receive
the `id` passed down from the `EuiFormRow` and respond to focus as
expected

  | <div align="center">Before</div> | <div align="center">After</div> |
  |--------|--------|
| ![Jul-28-2023
16-31-56](https://github.com/elastic/kibana/assets/8698078/125d2a75-bcec-452c-8682-85de3a44185b)
| ![Jul-28-2023
16-31-20](https://github.com/elastic/kibana/assets/8698078/935a17f1-4adc-4b86-811b-334a42e4627e)
|


### Checklist

- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] 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))
- [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)

### 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: Jatin Kathuria <jatin.kathuria@elastic.co>
  • Loading branch information
Heenawter and logeekal authored Aug 4, 2023
1 parent dd68393 commit 3763a5a
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export const ControlEditor = ({
<EuiFormRow
label={ControlGroupStrings.manageControl.displaySettings.getWidthInputTitle()}
>
<>
<div>
<EuiButtonGroup
color="primary"
legend={ControlGroupStrings.management.controlWidth.getWidthSwitchLegend()}
Expand All @@ -275,7 +275,7 @@ export const ControlEditor = ({
onChange={() => setCurrentGrow(!currentGrow)}
data-test-subj="control-editor-grow-switch"
/>
</>
</div>
</EuiFormRow>
)}
</EuiDescribedFormGroup>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
* Side Public License, v 1.
*/

import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { EuiPopover, EuiPopoverTitle, EuiSelectable, EuiSelectableProps } from '@elastic/eui';
import { EuiSelectable, EuiInputPopover, EuiSelectableProps } from '@elastic/eui';
import { DataViewListItem } from '@kbn/data-views-plugin/common';

import { ToolbarButton, ToolbarButtonProps } from '@kbn/kibana-react-plugin/public';

import './data_view_picker.scss';

export type DataViewTriggerProps = ToolbarButtonProps & {
label: string;
title?: string;
Expand All @@ -26,6 +23,7 @@ export function DataViewPicker({
onChangeDataViewId,
trigger,
selectableProps,
...other
}: {
dataViews: DataViewListItem[];
selectedDataViewId?: string;
Expand Down Expand Up @@ -61,20 +59,19 @@ export function DataViewPicker({
};

return (
<EuiPopover
button={createTrigger()}
isOpen={isPopoverOpen}
closePopover={() => setPopoverIsOpen(false)}
<EuiInputPopover
{...other}
ownFocus
fullWidth
display="block"
panelPaddingSize="s"
ownFocus
panelClassName="presDataViewPicker__panel"
isOpen={isPopoverOpen}
input={createTrigger()}
closePopover={() => setPopoverIsOpen(false)}
panelProps={{
'data-test-subj': 'data-view-picker-popover',
}}
>
<EuiPopoverTitle data-test-subj="data-view-picker-title">
{i18n.translate('presentationUtil.dataViewPicker.changeDataViewTitle', {
defaultMessage: 'Data view',
})}
</EuiPopoverTitle>
<EuiSelectable<{
key?: string;
label: string;
Expand Down Expand Up @@ -110,7 +107,7 @@ export function DataViewPicker({
</>
)}
</EuiSelectable>
</EuiPopover>
</EuiInputPopover>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@

.presFieldPickerFieldButtonActive {
background-color: transparentize($euiColorPrimary, .9);
}

.fieldPickerSelectable {
height: $euiSizeXXL * 9; // 40 * 9 = 360px

.presFieldPicker__fieldButton[aria-checked='true'] {
background-color: transparentize($euiColorPrimary, .9);
}

.euiSelectableMessage {
height: 100%;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
Expand All @@ -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)),
[dataView, filterPredicate, typesFilter]
);

Expand All @@ -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
Expand All @@ -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}
Expand All @@ -102,6 +110,7 @@ export const FieldPicker = ({

return (
<EuiSelectable
{...other}
{...selectableProps}
className={classNames('fieldPickerSelectable', {
fieldPickerSelectableLoading: selectableProps?.isLoading,
Expand All @@ -126,6 +135,7 @@ export const FieldPicker = ({
defaultMessage: 'Search field names',
}),
disabled: Boolean(selectableProps?.isLoading),
inputRef: setSearchRef,
}}
listProps={{
isVirtualized: true,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
}}
>
<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>
);
}
2 changes: 1 addition & 1 deletion test/functional/page_objects/dashboard_page_controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ export class DashboardPageControls extends FtrService {
this.log.debug(`Setting control data view to ${dataViewTitle}`);
await this.testSubjects.click('open-data-view-picker');
await this.retry.try(async () => {
await this.testSubjects.existOrFail('data-view-picker-title');
await this.testSubjects.existOrFail('data-view-picker-popover');
});
await this.testSubjects.click(`data-view-picker-${dataViewTitle}`);
}
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -4789,11 +4789,9 @@
"presentationUtil.labs.components.enabledStatusMessage": "Par défaut : {status}",
"presentationUtil.labs.components.noProjectsinSolutionMessage": "Aucun atelier actuellement dans {solutionName}.",
"presentationUtil.dashboardPicker.searchDashboardPlaceholder": "Recherche dans les tableaux de bord…",
"presentationUtil.dataViewPicker.changeDataViewTitle": "Vue de données",
"presentationUtil.fieldPicker.noFieldsLabel": "Aucun champ correspondant",
"presentationUtil.fieldPicker.selectableAriaLabel": "Sélectionner un champ",
"presentationUtil.fieldSearch.fieldFilterButtonLabel": "Filtrer par type",
"presentationUtil.fieldSearch.filterByTypeLabel": "Filtrer par type",
"presentationUtil.fieldSearch.searchPlaceHolder": "Rechercher les noms de champs",
"presentationUtil.labs.components.browserSwitchHelp": "Active l'atelier pour ce navigateur et persiste après sa fermeture.",
"presentationUtil.labs.components.browserSwitchName": "Navigateur",
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -4805,11 +4805,9 @@
"presentationUtil.labs.components.enabledStatusMessage": "デフォルト:{status}",
"presentationUtil.labs.components.noProjectsinSolutionMessage": "現在{solutionName}にラボはありません。",
"presentationUtil.dashboardPicker.searchDashboardPlaceholder": "ダッシュボードを検索...",
"presentationUtil.dataViewPicker.changeDataViewTitle": "データビュー",
"presentationUtil.fieldPicker.noFieldsLabel": "一致するがフィールドがありません",
"presentationUtil.fieldPicker.selectableAriaLabel": "フィールドを選択",
"presentationUtil.fieldSearch.fieldFilterButtonLabel": "タイプでフィルタリング",
"presentationUtil.fieldSearch.filterByTypeLabel": "タイプでフィルタリング",
"presentationUtil.fieldSearch.searchPlaceHolder": "検索フィールド名",
"presentationUtil.labs.components.browserSwitchHelp": "このブラウザーでラボを有効にします。ブラウザーを閉じた後も永続します。",
"presentationUtil.labs.components.browserSwitchName": "ブラウザー",
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -4804,11 +4804,9 @@
"presentationUtil.labs.components.enabledStatusMessage": "默认值:{status}",
"presentationUtil.labs.components.noProjectsinSolutionMessage": "{solutionName} 中当前没有实验。",
"presentationUtil.dashboardPicker.searchDashboardPlaceholder": "搜索仪表板......",
"presentationUtil.dataViewPicker.changeDataViewTitle": "数据视图",
"presentationUtil.fieldPicker.noFieldsLabel": "无匹配字段",
"presentationUtil.fieldPicker.selectableAriaLabel": "选择字段",
"presentationUtil.fieldSearch.fieldFilterButtonLabel": "按类型筛选",
"presentationUtil.fieldSearch.filterByTypeLabel": "按类型筛选",
"presentationUtil.fieldSearch.searchPlaceHolder": "搜索字段名称",
"presentationUtil.labs.components.browserSwitchHelp": "启用此浏览器的实验并在其关闭后继续保持。",
"presentationUtil.labs.components.browserSwitchName": "浏览器",
Expand Down

0 comments on commit 3763a5a

Please sign in to comment.