mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[A11y][TableListView] Refactor to use context to return focus (#188774)
## Summary Closes https://github.com/elastic/observability-dev/issues/3345 Refactoring the tags portion of the table list view to return focus after closing the tags popover --------- Co-authored-by: Sébastien Loix <sebastien.loix@elastic.co>
This commit is contained in:
parent
ac9165d5fc
commit
c11d5345b8
3 changed files with 60 additions and 56 deletions
|
@ -30,7 +30,7 @@ import type {
|
|||
} from '../table_list_view_table';
|
||||
import type { TableItemsRowActions } from '../types';
|
||||
import { TableSortSelect } from './table_sort_select';
|
||||
import { TagFilterPanel } from './tag_filter_panel';
|
||||
import { TagFilterPanel, TagFilterContextProvider } from './tag_filter_panel';
|
||||
import { useTagFilterPanel } from './use_tag_filter_panel';
|
||||
import type { Params as UseTagFilterPanelParams } from './use_tag_filter_panel';
|
||||
import type { SortColumnField } from './table_sort_select';
|
||||
|
@ -188,32 +188,9 @@ export function Table<T extends UserContentCommonSchema>({
|
|||
|
||||
return {
|
||||
type: 'custom_component',
|
||||
component: () => {
|
||||
return (
|
||||
<TagFilterPanel
|
||||
isPopoverOpen={isPopoverOpen}
|
||||
isInUse={isInUse}
|
||||
closePopover={closePopover}
|
||||
options={options}
|
||||
totalActiveFilters={totalActiveFilters}
|
||||
onFilterButtonClick={onFilterButtonClick}
|
||||
onSelectChange={onSelectChange}
|
||||
clearTagSelection={clearTagSelection}
|
||||
/>
|
||||
);
|
||||
},
|
||||
component: TagFilterPanel,
|
||||
};
|
||||
}, [
|
||||
isPopoverOpen,
|
||||
isInUse,
|
||||
isTaggingEnabled,
|
||||
closePopover,
|
||||
options,
|
||||
totalActiveFilters,
|
||||
onFilterButtonClick,
|
||||
onSelectChange,
|
||||
clearTagSelection,
|
||||
]);
|
||||
}, [isTaggingEnabled]);
|
||||
|
||||
const userFilterPanel = useMemo<SearchFilterConfig | null>(() => {
|
||||
return createdByEnabled
|
||||
|
@ -295,22 +272,33 @@ export function Table<T extends UserContentCommonSchema>({
|
|||
selectedUsers={tableFilter.createdBy}
|
||||
showNoUserOption={showNoUserOption}
|
||||
>
|
||||
<EuiInMemoryTable<T>
|
||||
itemId="id"
|
||||
items={visibleItems}
|
||||
columns={tableColumns}
|
||||
pagination={pagination}
|
||||
loading={isFetchingItems}
|
||||
message={noItemsMessage}
|
||||
selection={selection}
|
||||
search={search}
|
||||
executeQueryOptions={{ enabled: false }}
|
||||
sorting={sorting}
|
||||
onChange={onTableChange}
|
||||
data-test-subj="itemsInMemTable"
|
||||
rowHeader="attributes.title"
|
||||
tableCaption={tableCaption}
|
||||
/>
|
||||
<TagFilterContextProvider
|
||||
isPopoverOpen={isPopoverOpen}
|
||||
isInUse={isInUse}
|
||||
closePopover={closePopover}
|
||||
onFilterButtonClick={onFilterButtonClick}
|
||||
onSelectChange={onSelectChange}
|
||||
options={options}
|
||||
totalActiveFilters={totalActiveFilters}
|
||||
clearTagSelection={clearTagSelection}
|
||||
>
|
||||
<EuiInMemoryTable<T>
|
||||
itemId="id"
|
||||
items={visibleItems}
|
||||
columns={tableColumns}
|
||||
pagination={pagination}
|
||||
loading={isFetchingItems}
|
||||
message={noItemsMessage}
|
||||
selection={selection}
|
||||
search={search}
|
||||
executeQueryOptions={{ enabled: false }}
|
||||
sorting={sorting}
|
||||
onChange={onTableChange}
|
||||
data-test-subj="itemsInMemTable"
|
||||
rowHeader="attributes.title"
|
||||
tableCaption={tableCaption}
|
||||
/>
|
||||
</TagFilterContextProvider>
|
||||
</UserFilterContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ const saveBtnWrapperCSS = css`
|
|||
width: 100%;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
interface Context {
|
||||
clearTagSelection: () => void;
|
||||
closePopover: () => void;
|
||||
isPopoverOpen: boolean;
|
||||
|
@ -54,18 +54,28 @@ interface Props {
|
|||
onSelectChange: (updatedOptions: TagOptionItem[]) => void;
|
||||
}
|
||||
|
||||
export const TagFilterPanel: FC<Props> = ({
|
||||
isPopoverOpen,
|
||||
isInUse,
|
||||
options,
|
||||
totalActiveFilters,
|
||||
onFilterButtonClick,
|
||||
onSelectChange,
|
||||
closePopover,
|
||||
clearTagSelection,
|
||||
}) => {
|
||||
const TagFilterContext = React.createContext<Context | null>(null);
|
||||
|
||||
export const TagFilterContextProvider: FC<Context> = ({ children, ...props }) => {
|
||||
return <TagFilterContext.Provider value={props}>{children}</TagFilterContext.Provider>;
|
||||
};
|
||||
|
||||
export const TagFilterPanel: FC<{}> = ({}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { navigateToUrl, currentAppId$, getTagManagementUrl } = useServices();
|
||||
const componentContext = React.useContext(TagFilterContext);
|
||||
if (!componentContext)
|
||||
throw new Error('TagFilterPanel must be used within a TagFilterContextProvider');
|
||||
const {
|
||||
isPopoverOpen,
|
||||
isInUse,
|
||||
options,
|
||||
totalActiveFilters,
|
||||
onFilterButtonClick,
|
||||
onSelectChange,
|
||||
closePopover,
|
||||
clearTagSelection,
|
||||
} = componentContext;
|
||||
const isSearchVisible = options.length > 10;
|
||||
|
||||
const searchBoxCSS = css`
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { Query, EuiFlexGroup, EuiFlexItem, EuiText, EuiHealth, EuiBadge } from '@elastic/eui';
|
||||
import type { FieldValueOptionType } from '@elastic/eui';
|
||||
|
@ -54,21 +54,27 @@ export const useTagFilterPanel = ({
|
|||
const [options, setOptions] = useState<TagOptionItem[]>([]);
|
||||
const [tagSelection, setTagSelection] = useState<TagSelection>({});
|
||||
const totalActiveFilters = Object.keys(tagSelection).length;
|
||||
const onClickTime = useRef<number>(0);
|
||||
|
||||
const onSelectChange = useCallback(
|
||||
(updatedOptions: TagOptionItem[]) => {
|
||||
// Note: see data flow comment in useEffect() below
|
||||
// onSelectChange() handler is to support keyboard navigation. When user clicks on a tag
|
||||
// we call the onOptionClick() imperatively below and we don't need to do anything here.
|
||||
const timeSinceOptionClick = Date.now() - onClickTime.current;
|
||||
if (timeSinceOptionClick < 100) return;
|
||||
|
||||
const diff = updatedOptions.find((item, index) => item.checked !== options[index].checked);
|
||||
if (diff) {
|
||||
addOrRemoveIncludeTagFilter(diff.tag);
|
||||
}
|
||||
},
|
||||
[options, addOrRemoveIncludeTagFilter]
|
||||
[addOrRemoveIncludeTagFilter, options]
|
||||
);
|
||||
|
||||
const onOptionClick = useCallback(
|
||||
(tag: Tag) => (e: MouseEvent) => {
|
||||
const withModifierKey = (isMac && e.metaKey) || (!isMac && e.ctrlKey);
|
||||
onClickTime.current = Date.now();
|
||||
|
||||
if (withModifierKey) {
|
||||
addOrRemoveExcludeTagFilter(tag);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue