This is an automated email from the ASF dual-hosted git repository. rusackas pushed a commit to branch chore/enable-click-events-have-key-events in repository https://gitbox.apache.org/repos/asf/superset.git
commit 926013ab4121aea0f26954360093fd843c3dda6a Author: Evan Rusackas <[email protected]> AuthorDate: Mon Jul 13 12:23:23 2026 -0700 chore(a11y): enable jsx-a11y/click-events-have-key-events as error Adds a small, tested `handleKeyboardActivation` helper to @superset-ui/core that maps Enter/Space to a click handler, then applies it across the 80 violations (41 files) so every clickable non-interactive element is operable from the keyboard. Turns the rule on as "error". - New util `handleKeyboardActivation(callback)` returns an onKeyDown handler that fires the callback on Enter/Space (with preventDefault). Unit-tested, plus an ActionButton test covering the click and keyboard paths. - Elements that already had role="button"/tabIndex get a mirrored onKeyDown. Bare clickable divs (e.g. AgGrid CustomHeader menu items) also get role="button" + tabIndex so the handler is actually reachable. - Pure event-boundary wrappers (onClick only stops propagation) get a matching onKeyDown that stops propagation, rather than a no-op activation. - ModalTrigger's div trigger gains tabIndex so the new handler is focusable. This completes the keyboard-operability follow-up promised while enabling interactive-supports-focus in #42006. Co-Authored-By: Claude Opus 4.8 <[email protected]> --- superset-frontend/oxlint.json | 2 +- .../components/ActionButton/ActionButton.test.tsx | 14 +++++- .../src/components/ActionButton/index.tsx | 2 + .../src/components/ModalTrigger/index.tsx | 9 +++- .../src/components/PopoverSection/index.tsx | 2 + .../src/utils/handleKeyboardActivation.test.ts | 52 ++++++++++++++++++++++ .../src/utils/handleKeyboardActivation.ts | 43 ++++++++++++++++++ .../packages/superset-ui-core/src/utils/index.ts | 1 + .../src/AgGridTable/components/CustomHeader.tsx | 33 ++++++++++++-- .../src/react-pivottable/TableRenderers.tsx | 6 ++- superset-frontend/spec/helpers/shim.tsx | 2 +- .../TableExploreTree/TreeNodeRenderer.tsx | 11 +++++ superset-frontend/src/components/Chart/Chart.tsx | 8 +++- .../src/components/Chart/DrillBy/DrillByModal.tsx | 2 + .../components/Chart/DrillBy/DrillBySubmenu.tsx | 1 + .../DatasourceEditor/DatasourceEditor.tsx | 2 + .../src/components/ErrorMessage/ErrorAlert.tsx | 9 +++- .../src/components/ListView/ListView.tsx | 9 ++++ .../components/URLShortLinkButton/index.tsx | 1 + .../components/gridComponents/Tab/Tab.tsx | 4 ++ .../FilterBar/FilterConfigurationLink/index.tsx | 2 + .../FiltersConfigModal/FilterTitleContainer.tsx | 2 + .../FiltersConfigModal/ItemTitleContainer.tsx | 2 + .../src/explore/components/ControlHeader.tsx | 2 + .../components/DataTablesPane/DataTablesPane.tsx | 12 ++++- .../explore/components/DatasourcePanel/index.tsx | 10 ++++- .../explore/components/ExploreChartPanel/index.tsx | 11 ++++- .../components/ExploreViewContainer/index.tsx | 3 ++ .../ColumnConfigControl/ColumnConfigControl.tsx | 4 ++ .../DndColumnSelectControl/ColumnSelectPopover.tsx | 10 +++++ .../DndColumnSelectPopoverTitle.tsx | 2 + .../controls/LayerConfigsControl/LayerTreeItem.tsx | 3 ++ .../MetricControl/AdhocMetricEditPopover/index.tsx | 11 ++++- .../MetricControl/AdhocMetricEditPopoverTitle.tsx | 2 + .../components/controls/VizTypeControl/index.tsx | 12 ++++- .../alerts/components/NotificationMethod.tsx | 6 +++ .../src/features/charts/ChartCard.tsx | 11 ++++- .../src/features/dashboards/DashboardCard.tsx | 13 +++++- .../AddDataset/DatasetPanel/DatasetPanel.tsx | 6 +++ superset-frontend/src/pages/ChartList/index.tsx | 4 ++ .../src/pages/DashboardList/index.tsx | 4 ++ superset-frontend/src/pages/DatabaseList/index.tsx | 11 +++++ superset-frontend/src/pages/DatasetList/index.tsx | 15 +++++++ .../src/pages/RowLevelSecurityList/index.tsx | 4 +- superset-frontend/src/pages/Tags/index.tsx | 8 +++- superset-frontend/src/pages/TaskList/index.tsx | 4 ++ 46 files changed, 366 insertions(+), 21 deletions(-) diff --git a/superset-frontend/oxlint.json b/superset-frontend/oxlint.json index 1516d96689c..876a0d9cab8 100644 --- a/superset-frontend/oxlint.json +++ b/superset-frontend/oxlint.json @@ -201,7 +201,7 @@ "jsx-a11y/aria-role": ["error", { "ignoreNonDOM": false }], "jsx-a11y/aria-unsupported-elements": "error", "jsx-a11y/autocomplete-valid": "error", - "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/click-events-have-key-events": "error", "jsx-a11y/control-has-associated-label": "error", "jsx-a11y/heading-has-content": "error", "jsx-a11y/html-has-lang": "error", diff --git a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx index 123d5349505..06c6584d592 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { render, screen, userEvent } from '@superset-ui/core/spec'; +import { render, screen, userEvent, fireEvent } from '@superset-ui/core/spec'; import { Icons } from '@superset-ui/core/components/Icons'; import { ActionButton } from '.'; @@ -45,6 +45,18 @@ test('calls onClick when clicked', async () => { expect(onClick).toHaveBeenCalledTimes(1); }); +test('calls onClick when activated with the keyboard', () => { + const onClick = jest.fn(); + render(<ActionButton {...defaultProps} onClick={onClick} />); + + const button = screen.getByRole('button'); + fireEvent.keyDown(button, { key: 'Enter' }); + expect(onClick).toHaveBeenCalledTimes(1); + + fireEvent.keyDown(button, { key: ' ' }); + expect(onClick).toHaveBeenCalledTimes(2); +}); + test('renders with tooltip when tooltip prop is provided', async () => { const tooltipText = 'This is a tooltip'; render(<ActionButton {...defaultProps} tooltip={tooltipText} />); diff --git a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx index 336b8ca3d48..3e56efbc265 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx @@ -17,6 +17,7 @@ * under the License. */ +import { handleKeyboardActivation } from '../../utils'; import type { ReactElement, ReactNode } from 'react'; import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components'; import { css, useTheme } from '@apache-superset/core/theme'; @@ -55,6 +56,7 @@ export const ActionButton = ({ className="action-button" data-test={label} onClick={onClick} + onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined} > {icon} </span> diff --git a/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx index 4f477437027..e3762d1e5ad 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/index.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '../../utils'; import { forwardRef, ForwardedRef, @@ -126,7 +127,13 @@ export const ModalTrigger = forwardRef( </Button> )} {!isButton && ( - <div data-test="span-modal-trigger" onClick={open} role="button"> + <div + data-test="span-modal-trigger" + onClick={open} + onKeyDown={handleKeyboardActivation(open)} + role="button" + tabIndex={0} + > {triggerNode} </div> )} diff --git a/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx index 39009eba76a..902f7378b67 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/PopoverSection/index.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '../../utils'; import { MouseEventHandler, ReactNode } from 'react'; import { css, useTheme } from '@apache-superset/core/theme'; import { Icons } from '@superset-ui/core/components/Icons'; @@ -48,6 +49,7 @@ export default function PopoverSection({ role="button" tabIndex={0} onClick={onSelect} + onKeyDown={onSelect ? handleKeyboardActivation(onSelect) : undefined} css={css` display: flex; align-items: center; diff --git a/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.test.ts b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.test.ts new file mode 100644 index 00000000000..9fb42d4b252 --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.test.ts @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { KeyboardEvent } from 'react'; +import { handleKeyboardActivation } from './handleKeyboardActivation'; + +const makeEvent = (key: string) => { + const preventDefault = jest.fn(); + return { + event: { key, preventDefault } as unknown as KeyboardEvent, + preventDefault, + }; +}; + +test('invokes the callback and prevents default on Enter', () => { + const callback = jest.fn(); + const { event, preventDefault } = makeEvent('Enter'); + handleKeyboardActivation(callback)(event); + expect(callback).toHaveBeenCalledWith(event); + expect(preventDefault).toHaveBeenCalled(); +}); + +test('invokes the callback and prevents default on Space', () => { + const callback = jest.fn(); + const { event, preventDefault } = makeEvent(' '); + handleKeyboardActivation(callback)(event); + expect(callback).toHaveBeenCalledWith(event); + expect(preventDefault).toHaveBeenCalled(); +}); + +test('ignores other keys', () => { + const callback = jest.fn(); + const { event, preventDefault } = makeEvent('a'); + handleKeyboardActivation(callback)(event); + expect(callback).not.toHaveBeenCalled(); + expect(preventDefault).not.toHaveBeenCalled(); +}); diff --git a/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.ts b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.ts new file mode 100644 index 00000000000..b5470bfeecb --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/src/utils/handleKeyboardActivation.ts @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { KeyboardEvent } from 'react'; + +/** + * Builds an `onKeyDown` handler that invokes `callback` when the user presses + * Enter or Space, mirroring a click for keyboard users. Pair it with an + * element's `onClick` so `role="button"` (or similar) controls are operable + * from the keyboard, satisfying `jsx-a11y/click-events-have-key-events`. + * + * <div role="button" onClick={handleClick} + * onKeyDown={handleKeyboardActivation(handleClick)} /> + */ +export function handleKeyboardActivation( + callback: (event: KeyboardEvent) => void, +) { + return (event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + // Prevent the page from scrolling on Space and stop any duplicate + // default activation on Enter. + event.preventDefault(); + callback(event); + } + }; +} + +export default handleKeyboardActivation; diff --git a/superset-frontend/packages/superset-ui-core/src/utils/index.ts b/superset-frontend/packages/superset-ui-core/src/utils/index.ts index f947bdfd57e..797c7ab561f 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/index.ts @@ -27,6 +27,7 @@ export { default as makeSingleton } from './makeSingleton'; export { default as promiseTimeout } from './promiseTimeout'; export { default as removeDuplicates } from './removeDuplicates'; export { default as withLabel } from './withLabel'; +export { handleKeyboardActivation } from './handleKeyboardActivation'; export { lruCache } from './lruCache'; export { getSelectedText } from './getSelectedText'; export * from './featureFlags'; diff --git a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx index cb6e58e5491..024129ddb1d 100644 --- a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx +++ b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx @@ -19,6 +19,7 @@ * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { useRef, useState, useEffect } from 'react'; import { t } from '@apache-superset/core/translation'; import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons'; @@ -201,17 +202,35 @@ const CustomHeader: React.FC<CustomHeaderParams> = ({ const menuContent = ( <MenuContainer> {shouldShowAsc && ( - <div onClick={() => applySort('asc')} className="menu-item"> + <div + role="button" + tabIndex={0} + onClick={() => applySort('asc')} + onKeyDown={handleKeyboardActivation(() => applySort('asc'))} + className="menu-item" + > <ArrowUpOutlined /> {t('Sort Ascending')} </div> )} {shouldShowDesc && ( - <div onClick={() => applySort('desc')} className="menu-item"> + <div + role="button" + tabIndex={0} + onClick={() => applySort('desc')} + onKeyDown={handleKeyboardActivation(() => applySort('desc'))} + className="menu-item" + > <ArrowDownOutlined /> {t('Sort Descending')} </div> )} {currentSort && currentSort?.colId === colId && ( - <div onClick={clearSort} className="menu-item"> + <div + role="button" + tabIndex={0} + onClick={clearSort} + onKeyDown={handleKeyboardActivation(clearSort)} + className="menu-item" + > <span style={{ fontSize: 16 }}>↻</span> {t('Clear Sort')} </div> )} @@ -247,7 +266,13 @@ const CustomHeader: React.FC<CustomHeaderParams> = ({ isOpen={isMenuVisible} onClose={() => setMenuVisible(false)} > - <div className="three-dots-menu" onClick={handleMenuClick}> + <div + role="button" + tabIndex={0} + className="three-dots-menu" + onClick={handleMenuClick} + onKeyDown={handleKeyboardActivation(handleMenuClick)} + > <KebabMenu /> </div> </CustomPopover> diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx index d3e58763827..ccef0bc509a 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx @@ -26,7 +26,7 @@ import { useMemo, useEffect, } from 'react'; -import { safeHtmlSpan } from '@superset-ui/core'; +import { safeHtmlSpan, handleKeyboardActivation } from '@superset-ui/core'; import { t } from '@apache-superset/core/translation'; import { supersetTheme } from '@apache-superset/core/theme'; import PropTypes from 'prop-types'; @@ -172,6 +172,9 @@ function displayHeaderCell( tabIndex={0} className="toggle" onClick={onArrowClick || undefined} + onKeyDown={ + onArrowClick ? handleKeyboardActivation(onArrowClick) : undefined + } > {ArrowIcon} </span> @@ -1046,6 +1049,7 @@ export function TableRenderer(props: TableRendererProps) { onClick={e => { e.stopPropagation(); }} + onKeyDown={e => e.stopPropagation()} aria-label={ activeSortColumn === i ? `Sorted by ${columnName} ${sortingOrder[i] === 'asc' ? 'ascending' : 'descending'}` diff --git a/superset-frontend/spec/helpers/shim.tsx b/superset-frontend/spec/helpers/shim.tsx index 0bff723e4ca..b1c243357b9 100644 --- a/superset-frontend/spec/helpers/shim.tsx +++ b/superset-frontend/spec/helpers/shim.tsx @@ -119,7 +119,7 @@ jest.mock('@superset-ui/core/components/Icons/AsyncIcon', () => ({ const label = ariaLabel || fileName?.replace(/_/g, '-').toLowerCase() || ''; return ( - // eslint-disable-next-line jsx-a11y/no-static-element-interactions + // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events <span ref={ref} role={role || (onClick ? 'button' : 'img')} diff --git a/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx b/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx index dc3e83eaefe..af423f12b5f 100644 --- a/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx +++ b/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { css, styled, useTheme } from '@apache-superset/core/theme'; import { t } from '@apache-superset/core/translation'; import type { NodeRendererProps } from 'react-arborist'; @@ -186,6 +187,7 @@ const TreeNodeRenderer: React.FC<TreeNodeRendererProps> = ({ <span className="col-copy-action" onClick={e => e.stopPropagation()} + onKeyDown={e => e.stopPropagation()} > <ActionButton label={`copy-col-${data.name}`} @@ -215,6 +217,13 @@ const TreeNodeRenderer: React.FC<TreeNodeRendererProps> = ({ node.toggle(); } }} + onKeyDown={handleKeyboardActivation(() => { + if (node.isLeaf) { + node.select(); + } else { + node.toggle(); + } + })} > <span className="tree-node-icon" @@ -239,6 +248,7 @@ const TreeNodeRenderer: React.FC<TreeNodeRendererProps> = ({ <div className="side-action-container" onClick={e => e.stopPropagation()} + onKeyDown={e => e.stopPropagation()} > {pinnedSchemas.has(schema) && ( <div className="action-static"> @@ -306,6 +316,7 @@ const TreeNodeRenderer: React.FC<TreeNodeRendererProps> = ({ <div className="side-action-container" onClick={e => e.stopPropagation()} + onKeyDown={e => e.stopPropagation()} > {isPinned && ( <div className="action-static"> diff --git a/superset-frontend/src/components/Chart/Chart.tsx b/superset-frontend/src/components/Chart/Chart.tsx index 1519837c747..4cc5b08502b 100644 --- a/superset-frontend/src/components/Chart/Chart.tsx +++ b/superset-frontend/src/components/Chart/Chart.tsx @@ -30,6 +30,7 @@ import { type FilterState, type JsonObject, type AgGridChartState, + handleKeyboardActivation, } from '@superset-ui/core'; import { styled } from '@apache-superset/core/theme'; import type { ChartState, Datasource, ChartStatus } from 'src/explore/types'; @@ -465,7 +466,12 @@ function Chart({ {t( 'Click on "Create chart" button in the control panel on the left to preview a visualization or', )}{' '} - <span role="button" tabIndex={0} onClick={onQuery}> + <span + role="button" + tabIndex={0} + onClick={onQuery} + onKeyDown={handleKeyboardActivation(onQuery)} + > {t('click here')} </span> . diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx index 57149ee55e0..2e5cc764237 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx @@ -28,6 +28,7 @@ import { isDefined, ContextMenuFilters, AdhocFilter, + handleKeyboardActivation, } from '@superset-ui/core'; import { Alert } from '@apache-superset/core/components'; import { css, useTheme } from '@apache-superset/core/theme'; @@ -565,6 +566,7 @@ export default function DrillByModal({ role="button" tabIndex={0} onClick={route.onClick} + onKeyDown={handleKeyboardActivation(route.onClick)} css={css` cursor: pointer; `} diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx index d8053a8d600..d331cf528e0 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.tsx @@ -217,6 +217,7 @@ export const DrillBySubmenu = ({ role="menu" tabIndex={0} data-test="drill-by-submenu" + onKeyDown={e => e.stopPropagation()} css={css` width: 220px; max-width: 220px; diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx index d5a9034a023..ddb0f6d45dd 100644 --- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx @@ -36,6 +36,7 @@ import { SupersetClient, getClientErrorObject, getExtensionsRegistry, + handleKeyboardActivation, } from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/common'; import { t } from '@apache-superset/core/translation'; @@ -1760,6 +1761,7 @@ function DatasourceEditor({ role="button" tabIndex={0} onClick={onChangeEditMode} + onKeyDown={handleKeyboardActivation(onChangeEditMode)} > {isEditMode ? ( <Icons.UnlockOutlined diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx index 4eae54c6ad2..2965137df1d 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { useState } from 'react'; import { t } from '@apache-superset/core/translation'; import { Alert } from '@apache-superset/core/components'; @@ -103,6 +104,7 @@ export const ErrorAlert: React.FC<ErrorAlertProps> = ({ role="button" tabIndex={0} onClick={toggleDescription} + onKeyDown={handleKeyboardActivation(toggleDescription)} style={{ textDecoration: 'underline', cursor: 'pointer' }} > {isDescriptionVisible ? t('See less') : t('See more')} @@ -127,7 +129,12 @@ export const ErrorAlert: React.FC<ErrorAlertProps> = ({ return ( <> <Tooltip title={`${errorType}: ${message}`}> - <span role="button" onClick={() => setShowModal(true)} tabIndex={0}> + <span + role="button" + onClick={() => setShowModal(true)} + onKeyDown={handleKeyboardActivation(() => setShowModal(true))} + tabIndex={0} + > {renderTrigger()} </span> </Tooltip> diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index a88b2ebb736..f8129b50009 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { t } from '@apache-superset/core/translation'; import { Alert } from '@apache-superset/core/components'; import { styled } from '@apache-superset/core/theme'; @@ -255,6 +256,7 @@ const ViewModeToggle = ({ e.currentTarget.blur(); setMode('card'); }} + onKeyDown={handleKeyboardActivation(() => setMode('card'))} className={cx('toggle-button', { active: mode === 'card' })} > <Icons.AppstoreOutlined iconSize="xl" /> @@ -269,6 +271,7 @@ const ViewModeToggle = ({ e.currentTarget.blur(); setMode('table'); }} + onKeyDown={handleKeyboardActivation(() => setMode('table'))} className={cx('toggle-button', { active: mode === 'table' })} > <Icons.UnorderedListOutlined iconSize="xl" /> @@ -509,6 +512,9 @@ export function ListView<T extends object = any>({ tabIndex={0} className="deselect-all" onClick={() => toggleAllRowsSelected(false)} + onKeyDown={handleKeyboardActivation(() => + toggleAllRowsSelected(false), + )} > {t('Deselect all')} </span> @@ -544,6 +550,9 @@ export function ListView<T extends object = any>({ tabIndex={0} className="tag-btn" onClick={() => setShowBulkTagModal(true)} + onKeyDown={handleKeyboardActivation(() => + setShowBulkTagModal(true), + )} > {t('Add Tag')} </span> diff --git a/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx b/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx index 136c44425b9..6574bb69386 100644 --- a/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx +++ b/superset-frontend/src/dashboard/components/URLShortLinkButton/index.tsx @@ -106,6 +106,7 @@ export default function URLShortLinkButton({ onClick={e => { e.stopPropagation(); }} + onKeyDown={e => e.stopPropagation()} > <CopyToClipboard text={shortUrl} diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.tsx b/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.tsx index 9bdbf9221c2..6f6621c7282 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.tsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { Fragment, useCallback, @@ -351,6 +352,9 @@ const Tab = (props: TabProps): ReactElement => { role="button" tabIndex={0} onClick={() => dispatch(setEditMode(true))} + onKeyDown={handleKeyboardActivation(() => + dispatch(setEditMode(true)), + )} > {t('edit mode')} </span> diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx index 367072fbab9..76573405681 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { ReactNode, FC, memo } from 'react'; import { getFilterBarTestId } from '../utils'; @@ -32,6 +33,7 @@ export const FilterConfigurationLink: FC<FCBProps> = ({ <div {...getFilterBarTestId('create-filter')} onClick={onClick} + onKeyDown={handleKeyboardActivation(onClick)} role="button" tabIndex={0} > diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx index e5420ad1b63..c3026f18efe 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { forwardRef, useCallback, useState } from 'react'; import { t } from '@apache-superset/core/translation'; @@ -199,6 +200,7 @@ const FilterTitleContainer = forwardRef<HTMLDivElement, Props>( e.preventDefault(); restoreFilter(id); }} + onKeyDown={handleKeyboardActivation(() => restoreFilter(id))} > {t('Undo?')} </span> diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx index 388421b5ac1..7904835fc4c 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { forwardRef, useState } from 'react'; import { t } from '@apache-superset/core/translation'; @@ -167,6 +168,7 @@ const ItemTitleContainer = forwardRef<HTMLDivElement, Props>( e.preventDefault(); restoreItem(id); }} + onKeyDown={handleKeyboardActivation(() => restoreItem(id))} > {t('Undo?')} </span> diff --git a/superset-frontend/src/explore/components/ControlHeader.tsx b/superset-frontend/src/explore/components/ControlHeader.tsx index a2bbf047c31..fd62cbaf739 100644 --- a/superset-frontend/src/explore/components/ControlHeader.tsx +++ b/superset-frontend/src/explore/components/ControlHeader.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { FC, ReactNode } from 'react'; import { t } from '@apache-superset/core/translation'; import { css, useTheme, SupersetTheme } from '@apache-superset/core/theme'; @@ -139,6 +140,7 @@ const ControlHeader: FC<ControlHeaderProps> = ({ role="button" tabIndex={0} onClick={onClick} + onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined} style={{ cursor: onClick ? 'pointer' : '' }} > {label} diff --git a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx index 55994e58d82..6c40bba70d9 100644 --- a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx +++ b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx @@ -18,7 +18,11 @@ */ import { useCallback, useEffect, useMemo, useState, MouseEvent } from 'react'; import { t } from '@apache-superset/core/translation'; -import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; +import { + isFeatureEnabled, + FeatureFlag, + handleKeyboardActivation, +} from '@superset-ui/core'; import { styled } from '@apache-superset/core/theme'; import { Icons } from '@superset-ui/core/components/Icons'; import Tabs from '@superset-ui/core/components/Tabs'; @@ -187,6 +191,9 @@ export const DataTablesPane = ({ role="button" tabIndex={0} onClick={() => handleCollapseChange(false)} + onKeyDown={handleKeyboardActivation(() => + handleCollapseChange(false), + )} > {caretIcon} </span> @@ -195,6 +202,9 @@ export const DataTablesPane = ({ role="button" tabIndex={0} onClick={() => handleCollapseChange(true)} + onKeyDown={handleKeyboardActivation(() => + handleCollapseChange(true), + )} > {caretIcon} </span> diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx index 7e9d5244130..c72efc8c5fd 100644 --- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx +++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx @@ -18,7 +18,12 @@ */ import { useContext, useDeferredValue, useMemo, useState } from 'react'; import { t } from '@apache-superset/core/translation'; -import { DatasourceType, Metric, QueryFormData } from '@superset-ui/core'; +import { + DatasourceType, + Metric, + QueryFormData, + handleKeyboardActivation, +} from '@superset-ui/core'; import { Alert } from '@apache-superset/core/components'; import { css, styled, useTheme } from '@apache-superset/core/theme'; @@ -290,6 +295,9 @@ export default function DataSourcePanel({ role="button" tabIndex={0} onClick={() => setShowSaveDatasetModal(true)} + onKeyDown={handleKeyboardActivation(() => + setShowSaveDatasetModal(true), + )} className="add-dataset-alert-description" > {t('Create a dataset')} diff --git a/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx b/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx index 3145bda73a2..e90f9e708ea 100644 --- a/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx +++ b/superset-frontend/src/explore/components/ExploreChartPanel/index.tsx @@ -30,6 +30,7 @@ import { QueryFormData, JsonObject, getExtensionsRegistry, + handleKeyboardActivation, } from '@superset-ui/core'; import { Alert } from '@apache-superset/core/components'; import { css, styled, useTheme } from '@apache-superset/core/theme'; @@ -396,6 +397,9 @@ const ExploreChartPanel = ({ role="button" tabIndex={0} onClick={() => setShowDatasetModal(true)} + onKeyDown={handleKeyboardActivation(() => + setShowDatasetModal(true), + )} css={{ textDecoration: 'underline' }} > {t('Create a dataset')} @@ -420,7 +424,12 @@ const ExploreChartPanel = ({ {t( 'You updated the values in the control panel, but the chart was not updated automatically. Run the query by clicking on the "Update chart" button or', )}{' '} - <span role="button" tabIndex={0} onClick={onQuery}> + <span + role="button" + tabIndex={0} + onClick={onQuery} + onKeyDown={handleKeyboardActivation(onQuery)} + > {t('click here')} </span> . diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx index aebaa64da17..b0ca4ac0868 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx @@ -37,6 +37,7 @@ import { MatrixifyFormData, DatasourceType, ensureIsArray, + handleKeyboardActivation, } from '@superset-ui/core'; import { ControlStateMapping, @@ -998,6 +999,7 @@ function ExploreViewContainer(props: ExploreViewContainerProps) { tabIndex={0} className="action-button" onClick={toggleCollapse} + onKeyDown={handleKeyboardActivation(toggleCollapse)} > <Icons.VerticalAlignTopOutlined iconSize="xl" @@ -1023,6 +1025,7 @@ function ExploreViewContainer(props: ExploreViewContainerProps) { <div className="sidebar" onClick={toggleCollapse} + onKeyDown={handleKeyboardActivation(toggleCollapse)} data-test="open-datasource-tab" role="button" tabIndex={0} diff --git a/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx index 239deefaac7..201a90f3f23 100644 --- a/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx +++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { useMemo, useState } from 'react'; import { t } from '@apache-superset/core/translation'; import { useTheme } from '@apache-superset/core/theme'; @@ -180,6 +181,9 @@ export default function ColumnConfigControl<T extends ColumnConfig>({ }, }} onClick={() => setShowAllColumns(!showAllColumns)} + onKeyDown={handleKeyboardActivation(() => + setShowAllColumns(!showAllColumns), + )} > {showAllColumns ? ( <> diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx index 1e69be2c45e..16d0aa344cc 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx @@ -35,6 +35,7 @@ import { DatasourceType, Metric, QueryFormMetric, + handleKeyboardActivation, } from '@superset-ui/core'; import { styled, css } from '@apache-superset/core/theme'; import { ColumnMeta, isSavedExpression } from '@superset-ui/chart-controls'; @@ -476,6 +477,9 @@ const ColumnSelectPopover = ({ role="button" tabIndex={0} onClick={setDatasetAndClose} + onKeyDown={handleKeyboardActivation( + setDatasetAndClose, + )} > {t('Create a dataset')} </span>{' '} @@ -487,6 +491,9 @@ const ColumnSelectPopover = ({ role="button" tabIndex={0} onClick={setDatasetAndClose} + onKeyDown={handleKeyboardActivation( + setDatasetAndClose, + )} > {t('Create a dataset')} </span>{' '} @@ -521,6 +528,9 @@ const ColumnSelectPopover = ({ role="button" tabIndex={0} onClick={setDatasetAndClose} + onKeyDown={handleKeyboardActivation( + setDatasetAndClose, + )} > {t('Create a dataset')} </span>{' '} diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx index 05c3cd3744c..38b3a47a73d 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { ChangeEvent, useCallback, useState } from 'react'; import { t } from '@apache-superset/core/translation'; import { styled, useTheme } from '@apache-superset/core/theme'; @@ -96,6 +97,7 @@ export const DndColumnSelectPopoverTitle = ({ onMouseOut={onMouseOut} onFocus={onMouseOver} onClick={onClick} + onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined} onBlur={onBlur} role="button" tabIndex={0} diff --git a/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx b/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx index 72f519ff7d8..ce99c88acd3 100644 --- a/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx +++ b/superset-frontend/src/explore/components/controls/LayerConfigsControl/LayerTreeItem.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { Icons } from '@superset-ui/core/components/Icons'; import { Button } from '@superset-ui/core/components'; import { Tag } from 'src/components'; @@ -47,6 +48,7 @@ export const LayerTreeItem: FC<LayerTreeItemProps> = ({ <span className="layer-tree-item-type" onClick={onEditTag} + onKeyDown={handleKeyboardActivation(onEditTag)} role="button" tabIndex={0} > @@ -55,6 +57,7 @@ export const LayerTreeItem: FC<LayerTreeItemProps> = ({ <span className="layer-tree-item-title" onClick={onEditTag} + onKeyDown={handleKeyboardActivation(onEditTag)} role="button" tabIndex={0} > diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx index ccbd3a2deab..ce0fea1968b 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/index.tsx @@ -19,7 +19,12 @@ /* eslint-disable camelcase */ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; -import { isDefined, ensureIsArray, DatasourceType } from '@superset-ui/core'; +import { + isDefined, + ensureIsArray, + DatasourceType, + handleKeyboardActivation, +} from '@superset-ui/core'; import { t } from '@apache-superset/core/translation'; import type { editors } from '@apache-superset/core'; import { styled } from '@apache-superset/core/theme'; @@ -498,6 +503,10 @@ function AdhocMetricEditPopover({ handleDatasetModal?.(true); onClose(); }} + onKeyDown={handleKeyboardActivation(() => { + handleDatasetModal?.(true); + onClose(); + })} > {t('Create a dataset')} </span> diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx index 57d5ed8e3c2..e9cc283f458 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { ChangeEventHandler, FocusEvent, @@ -120,6 +121,7 @@ const AdhocMetricEditPopoverTitle: FC<AdhocMetricEditPopoverTitleProps> = ({ onMouseOut={handleMouseOut} onFocus={handleMouseOver} onClick={handleClick} + onKeyDown={handleKeyboardActivation(handleClick)} onBlur={handleBlur} role="button" tabIndex={0} diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx index ad11bd73da9..10c9fceff3f 100644 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx @@ -18,7 +18,10 @@ */ import { useCallback, useState } from 'react'; import { t } from '@apache-superset/core/translation'; -import { getChartMetadataRegistry } from '@superset-ui/core'; +import { + getChartMetadataRegistry, + handleKeyboardActivation, +} from '@superset-ui/core'; import { css, styled, SupersetTheme } from '@apache-superset/core/theme'; import { usePluginContext } from 'src/components'; import { Icons, Modal } from '@superset-ui/core/components'; @@ -112,7 +115,12 @@ const VizTypeControl = ({ color: ${theme.colorTextTertiary}; `} > - <span role="button" tabIndex={0} onClick={openModal}> + <span + role="button" + tabIndex={0} + onClick={openModal} + onKeyDown={handleKeyboardActivation(openModal)} + > {t('View all charts')} </span> </div> diff --git a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx index 1a5df776491..6699ed02b67 100644 --- a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx +++ b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx @@ -31,6 +31,7 @@ import { JsonResponse, SupersetClient, isFeatureEnabled, + handleKeyboardActivation, } from '@superset-ui/core'; import { styled, useTheme } from '@apache-superset/core/theme'; import { Icons } from '@superset-ui/core/components/Icons'; @@ -574,6 +575,7 @@ export const NotificationMethod: FunctionComponent<NotificationMethodProps> = ({ tabIndex={0} className="delete-button" onClick={() => onRemove(index)} + onKeyDown={handleKeyboardActivation(() => onRemove(index))} aria-label={t('Remove notification method')} > <Icons.DeleteOutlined iconSize="l" /> @@ -773,6 +775,7 @@ export const NotificationMethod: FunctionComponent<NotificationMethodProps> = ({ role="button" tabIndex={0} onClick={() => setCcVisible(true)} + onKeyDown={handleKeyboardActivation(() => setCcVisible(true))} style={{ display: ccVisible ? 'none' : 'inline-flex' }} > <Icons.MailOutlined iconSize="xs" className="icon" /> @@ -783,6 +786,9 @@ export const NotificationMethod: FunctionComponent<NotificationMethodProps> = ({ role="button" tabIndex={0} onClick={() => setBccVisible(true)} + onKeyDown={handleKeyboardActivation(() => + setBccVisible(true), + )} style={{ display: bccVisible ? 'none' : 'inline-flex' }} > <Icons.MailOutlined iconSize="xs" className="icon" /> diff --git a/superset-frontend/src/features/charts/ChartCard.tsx b/superset-frontend/src/features/charts/ChartCard.tsx index 9a7c0569ed2..b55904f7144 100644 --- a/superset-frontend/src/features/charts/ChartCard.tsx +++ b/superset-frontend/src/features/charts/ChartCard.tsx @@ -17,7 +17,11 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; -import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; +import { + isFeatureEnabled, + FeatureFlag, + handleKeyboardActivation, +} from '@superset-ui/core'; import { css } from '@apache-superset/core/theme'; import { Link, useHistory } from 'react-router-dom'; import { @@ -86,6 +90,7 @@ export default function ChartCard({ role="button" tabIndex={0} onClick={() => openChartEditModal(chart)} + onKeyDown={handleKeyboardActivation(() => openChartEditModal(chart))} > <Icons.EditOutlined iconSize="l" @@ -107,6 +112,9 @@ export default function ChartCard({ role="button" tabIndex={0} onClick={() => handleBulkChartExport([chart])} + onKeyDown={handleKeyboardActivation(() => + handleBulkChartExport([chart]), + )} > <Icons.UploadOutlined iconSize="l" @@ -150,6 +158,7 @@ export default function ChartCard({ tabIndex={0} className="action-button" onClick={confirmDelete} + onKeyDown={handleKeyboardActivation(confirmDelete)} > <Icons.DeleteOutlined iconSize="l" diff --git a/superset-frontend/src/features/dashboards/DashboardCard.tsx b/superset-frontend/src/features/dashboards/DashboardCard.tsx index 6b45288641a..1b1096a24dc 100644 --- a/superset-frontend/src/features/dashboards/DashboardCard.tsx +++ b/superset-frontend/src/features/dashboards/DashboardCard.tsx @@ -18,7 +18,11 @@ */ import { Link, useHistory } from 'react-router-dom'; import { t } from '@apache-superset/core/translation'; -import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; +import { + isFeatureEnabled, + FeatureFlag, + handleKeyboardActivation, +} from '@superset-ui/core'; import { CardStyles } from 'src/views/CRUD/utils'; import { FaveStar, @@ -80,6 +84,9 @@ function DashboardCard({ tabIndex={0} className="action-button" onClick={() => openDashboardEditModal(dashboard)} + onKeyDown={handleKeyboardActivation(() => + openDashboardEditModal(dashboard), + )} data-test="dashboard-card-option-edit-button" > <Icons.EditOutlined iconSize="l" data-test="edit-alt" /> {t('Edit')} @@ -96,6 +103,9 @@ function DashboardCard({ role="button" tabIndex={0} onClick={() => handleBulkDashboardExport([dashboard])} + onKeyDown={handleKeyboardActivation(() => + handleBulkDashboardExport([dashboard]), + )} className="action-button" data-test="dashboard-card-option-export-button" > @@ -114,6 +124,7 @@ function DashboardCard({ tabIndex={0} className="action-button" onClick={() => onDelete(dashboard)} + onKeyDown={handleKeyboardActivation(() => onDelete(dashboard))} data-test="dashboard-card-option-delete-button" > <Icons.DeleteOutlined iconSize="l" /> {t('Delete')} diff --git a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx index 6bf357a1f3b..ad29c510699 100644 --- a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx +++ b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { handleKeyboardActivation } from '@superset-ui/core'; import { t } from '@apache-superset/core/translation'; import { Alert } from '@apache-superset/core/components'; import { styled } from '@apache-superset/core/theme'; @@ -235,6 +236,11 @@ const renderExistingDatasetAlert = (dataset?: DatasetObject) => ( openInNewTab(stripAppRoot(dataset.explore_url)); } }} + onKeyDown={handleKeyboardActivation(() => { + if (dataset?.explore_url) { + openInNewTab(stripAppRoot(dataset.explore_url)); + } + })} tabIndex={0} className="view-dataset-button" > diff --git a/superset-frontend/src/pages/ChartList/index.tsx b/superset-frontend/src/pages/ChartList/index.tsx index 45e65370170..00ea34af07e 100644 --- a/superset-frontend/src/pages/ChartList/index.tsx +++ b/superset-frontend/src/pages/ChartList/index.tsx @@ -25,6 +25,7 @@ import { JsonResponse, SupersetClient, isMatrixifyEnabled, + handleKeyboardActivation, } from '@superset-ui/core'; import { css, styled } from '@apache-superset/core/theme'; import { useState, useMemo, useCallback } from 'react'; @@ -544,6 +545,7 @@ function ChartList(props: ChartListProps) { tabIndex={0} className="action-button" onClick={openEditModal} + onKeyDown={handleKeyboardActivation(openEditModal)} > <Icons.EditOutlined data-test="edit-alt" iconSize="l" /> </span> @@ -561,6 +563,7 @@ function ChartList(props: ChartListProps) { tabIndex={0} className="action-button" onClick={handleExport} + onKeyDown={handleKeyboardActivation(handleExport)} > <Icons.UploadOutlined iconSize="l" /> </span> @@ -589,6 +592,7 @@ function ChartList(props: ChartListProps) { tabIndex={0} className="action-button" onClick={confirmDelete} + onKeyDown={handleKeyboardActivation(confirmDelete)} > <Icons.DeleteOutlined iconSize="l" /> </span> diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx index 100669050bb..91d3ef3bc80 100644 --- a/superset-frontend/src/pages/DashboardList/index.tsx +++ b/superset-frontend/src/pages/DashboardList/index.tsx @@ -21,6 +21,7 @@ import { isFeatureEnabled, FeatureFlag, SupersetClient, + handleKeyboardActivation, } from '@superset-ui/core'; import { styled } from '@apache-superset/core/theme'; import { useSelector } from 'react-redux'; @@ -503,6 +504,7 @@ function DashboardList(props: DashboardListProps) { tabIndex={0} className="action-button" onClick={handleEdit} + onKeyDown={handleKeyboardActivation(handleEdit)} > <Icons.EditOutlined data-test="edit-alt" iconSize="l" /> </span> @@ -520,6 +522,7 @@ function DashboardList(props: DashboardListProps) { tabIndex={0} className="action-button" onClick={handleExport} + onKeyDown={handleKeyboardActivation(handleExport)} > <Icons.UploadOutlined iconSize="l" /> </span> @@ -548,6 +551,7 @@ function DashboardList(props: DashboardListProps) { tabIndex={0} className="action-button" onClick={confirmDelete} + onKeyDown={handleKeyboardActivation(confirmDelete)} > <Icons.DeleteOutlined iconSize="l" diff --git a/superset-frontend/src/pages/DatabaseList/index.tsx b/superset-frontend/src/pages/DatabaseList/index.tsx index aa284e26095..e36c3f38819 100644 --- a/superset-frontend/src/pages/DatabaseList/index.tsx +++ b/superset-frontend/src/pages/DatabaseList/index.tsx @@ -22,6 +22,7 @@ import { SupersetClient, isFeatureEnabled, FeatureFlag, + handleKeyboardActivation, } from '@superset-ui/core'; import { css, styled, useTheme } from '@apache-superset/core/theme'; import { useState, useMemo, useEffect, useCallback } from 'react'; @@ -697,6 +698,9 @@ function DatabaseList({ tabIndex={0} className="action-button" onClick={() => setSlCurrentlyDeleting(original)} + onKeyDown={handleKeyboardActivation(() => + setSlCurrentlyDeleting(original), + )} > <Icons.DeleteOutlined iconSize="l" /> </span> @@ -715,6 +719,9 @@ function DatabaseList({ onClick={() => setSlCurrentlyEditing(original.uuid ?? null) } + onKeyDown={handleKeyboardActivation(() => + setSlCurrentlyEditing(original.uuid ?? null), + )} > <Icons.EditOutlined iconSize="l" /> </span> @@ -746,6 +753,7 @@ function DatabaseList({ tabIndex={0} className="action-button" onClick={handleEdit} + onKeyDown={handleKeyboardActivation(handleEdit)} > <Icons.EditOutlined data-test="edit-alt" iconSize="l" /> </span> @@ -762,6 +770,7 @@ function DatabaseList({ tabIndex={0} className="action-button" onClick={handleExport} + onKeyDown={handleKeyboardActivation(handleExport)} > <Icons.UploadOutlined iconSize="l" /> </span> @@ -779,6 +788,7 @@ function DatabaseList({ tabIndex={0} className="action-button" onClick={handleSync} + onKeyDown={handleKeyboardActivation(handleSync)} > <Icons.SyncOutlined iconSize="l" /> </span> @@ -791,6 +801,7 @@ function DatabaseList({ className="action-button" data-test="database-delete" onClick={handleDelete} + onKeyDown={handleKeyboardActivation(handleDelete)} > <Tooltip id="delete-action-tooltip" diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx index 4c89289077e..84c4d306d2d 100644 --- a/superset-frontend/src/pages/DatasetList/index.tsx +++ b/superset-frontend/src/pages/DatasetList/index.tsx @@ -22,6 +22,7 @@ import { SupersetClient, isFeatureEnabled, FeatureFlag, + handleKeyboardActivation, } from '@superset-ui/core'; import { styled, useTheme, css } from '@apache-superset/core/theme'; import { @@ -869,6 +870,9 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({ tabIndex={0} className="action-button" onClick={() => handleSemanticViewDelete(original)} + onKeyDown={handleKeyboardActivation(() => + handleSemanticViewDelete(original), + )} > <Icons.DeleteOutlined iconSize="l" /> </span> @@ -886,6 +890,9 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({ tabIndex={0} className="action-button" onClick={() => setSvCurrentlyEditing(original)} + onKeyDown={handleKeyboardActivation(() => + setSvCurrentlyEditing(original), + )} > <Icons.EditOutlined iconSize="l" /> </span> @@ -932,6 +939,11 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({ tabIndex={0} className={`action-button ${allowEdit ? '' : 'disabled'}`} onClick={allowEdit ? handleEdit : undefined} + onKeyDown={ + allowEdit + ? handleKeyboardActivation(handleEdit) + : undefined + } > <Icons.EditOutlined iconSize="l" /> </span> @@ -949,6 +961,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({ tabIndex={0} className="action-button" onClick={handleExport} + onKeyDown={handleKeyboardActivation(handleExport)} > <Icons.UploadOutlined iconSize="l" /> </span> @@ -966,6 +979,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({ tabIndex={0} className="action-button" onClick={handleDuplicate} + onKeyDown={handleKeyboardActivation(handleDuplicate)} > <Icons.CopyOutlined iconSize="l" /> </span> @@ -983,6 +997,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({ tabIndex={0} className="action-button" onClick={handleDelete} + onKeyDown={handleKeyboardActivation(handleDelete)} > <Icons.DeleteOutlined iconSize="l" /> </span> diff --git a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx index 4d77911ba1d..689c493e49e 100644 --- a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx +++ b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx @@ -17,7 +17,7 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; -import { SupersetClient } from '@superset-ui/core'; +import { SupersetClient, handleKeyboardActivation } from '@superset-ui/core'; import { useCallback, useMemo, useState } from 'react'; import { ConfirmStatusChange, Tooltip } from '@superset-ui/core/components'; import { @@ -209,6 +209,7 @@ function RowLevelSecurityList(props: RLSProps) { tabIndex={0} className="action-button" onClick={handleEdit} + onKeyDown={handleKeyboardActivation(handleEdit)} > <Icons.EditOutlined data-test="edit-alt" iconSize="l" /> </span> @@ -236,6 +237,7 @@ function RowLevelSecurityList(props: RLSProps) { tabIndex={0} className="action-button" onClick={confirmDelete} + onKeyDown={handleKeyboardActivation(confirmDelete)} > <Icons.DeleteOutlined data-test="rls-list-trash-icon" diff --git a/superset-frontend/src/pages/Tags/index.tsx b/superset-frontend/src/pages/Tags/index.tsx index ffd7bd591cc..00cfbe6c400 100644 --- a/superset-frontend/src/pages/Tags/index.tsx +++ b/superset-frontend/src/pages/Tags/index.tsx @@ -18,7 +18,11 @@ */ import { useMemo, useState } from 'react'; import { t } from '@apache-superset/core/translation'; -import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; +import { + isFeatureEnabled, + FeatureFlag, + handleKeyboardActivation, +} from '@superset-ui/core'; import { Actions, createErrorHandler, @@ -219,6 +223,7 @@ function TagList(props: TagListProps) { tabIndex={0} className="action-button" onClick={confirmDelete} + onKeyDown={handleKeyboardActivation(confirmDelete)} > <Icons.DeleteOutlined data-test="dashboard-list-trash-icon" @@ -240,6 +245,7 @@ function TagList(props: TagListProps) { tabIndex={0} className="action-button" onClick={handleEdit} + onKeyDown={handleKeyboardActivation(handleEdit)} > <Icons.EditOutlined data-test="edit-alt" iconSize="l" /> </span> diff --git a/superset-frontend/src/pages/TaskList/index.tsx b/superset-frontend/src/pages/TaskList/index.tsx index 41b25d65602..3acac2d5177 100644 --- a/superset-frontend/src/pages/TaskList/index.tsx +++ b/superset-frontend/src/pages/TaskList/index.tsx @@ -21,6 +21,7 @@ import { FeatureFlag, isFeatureEnabled, SupersetClient, + handleKeyboardActivation, } from '@superset-ui/core'; import { useTheme } from '@apache-superset/core/theme'; import { t } from '@apache-superset/core/translation'; @@ -519,6 +520,9 @@ function TaskList({ addDangerToast, addSuccessToast, user }: TaskListProps) { tabIndex={0} className="action-button" onClick={() => openCancelModal(original)} + onKeyDown={handleKeyboardActivation(() => + openCancelModal(original), + )} > <Icons.StopOutlined iconSize="l" /> </span>
