diff --git a/superset-frontend/oxlint.json b/superset-frontend/oxlint.json
index ee162217cf9..6f168d1f78f 100644
--- a/superset-frontend/oxlint.json
+++ b/superset-frontend/oxlint.json
@@ -215,6 +215,7 @@
"jsx-a11y/no-noninteractive-tabindex": "error",
"jsx-a11y/no-redundant-roles": "error",
"jsx-a11y/no-static-element-interactions": "error",
+ "jsx-a11y/prefer-tag-over-role": "error",
"jsx-a11y/role-has-required-aria-props": "error",
"jsx-a11y/role-supports-aria-props": "error",
"jsx-a11y/scope": "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 06c6584d592..fb33b4b12cf 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, fireEvent } from '@superset-ui/core/spec';
+import { render, screen, userEvent } from '@superset-ui/core/spec';
import { Icons } from '@superset-ui/core/components/Icons';
import { ActionButton } from '.';
@@ -45,18 +45,6 @@ test('calls onClick when clicked', async () => {
expect(onClick).toHaveBeenCalledTimes(1);
});
-test('calls onClick when activated with the keyboard', () => {
- const onClick = jest.fn();
- render();
-
- 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();
@@ -115,6 +103,6 @@ test('has proper accessibility attributes', () => {
render();
const button = screen.getByRole('button');
- expect(button).toHaveAttribute('tabIndex', '0');
- expect(button).toHaveAttribute('role', 'button');
+ expect(button.tagName).toBe('BUTTON');
+ expect(button).toHaveAttribute('type', 'button');
});
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 3e56efbc265..e0bb6f560db 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,8 +17,8 @@
* under the License.
*/
-import { handleKeyboardActivation } from '../../utils';
import type { ReactElement, ReactNode } from 'react';
+import cx from 'classnames';
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
import { css, useTheme } from '@apache-superset/core/theme';
@@ -28,6 +28,9 @@ export interface ActionProps {
placement?: TooltipPlacement;
icon: ReactNode;
onClick: () => void;
+ className?: string;
+ disabled?: boolean;
+ dataTest?: string;
}
export const ActionButton = ({
@@ -36,30 +39,45 @@ export const ActionButton = ({
placement,
icon,
onClick,
+ className,
+ disabled = false,
+ dataTest,
}: ActionProps) => {
const theme = useTheme();
const actionButton = (
-
{icon}
-
+
);
const tooltipId = `${label.replaceAll(' ', '-').toLowerCase()}-tooltip`;
diff --git a/superset-frontend/packages/superset-ui-core/src/components/ButtonGroup/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/ButtonGroup/index.tsx
index 89c373c1e2b..4b1b33cbad8 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/ButtonGroup/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/ButtonGroup/index.tsx
@@ -21,7 +21,11 @@ import type { ButtonGroupProps } from './types';
export function ButtonGroup(props: ButtonGroupProps) {
const { className, children } = props;
return (
+ // role="group" is the correct ARIA pattern for a generic button toolbar;
+ // the suggested native tags (fieldset, etc.) carry unrelated form
+ // semantics and unwanted default browser styling.
since it's a component tree, not an
+ // image file reference.
+ // eslint-disable-next-line jsx-a11y/prefer-tag-over-role
css`
+ appearance: none;
+ border: none;
+ background: none;
+ font: inherit;
font-size: ${theme.fontSizeXL}px;
display: flex;
padding: 0 0 0 ${theme.sizeUnit * 2}px;
@@ -55,12 +59,10 @@ export const FaveStar = ({
const content = (
{isStarred ? (
)
}
+ // input[type=password] doesn't get an implicit "textbox" role
+ // (unlike other text inputs), so this explicit override is
+ // needed for it to be discoverable via role-based queries/AT.
+ // eslint-disable-next-line jsx-a11y/prefer-tag-over-role
role="textbox"
/>
) : renderAsTextArea ? (
diff --git a/superset-frontend/packages/superset-ui-core/src/components/IconButton/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/IconButton/index.tsx
index 31a7899d99b..7c6e35d3d3e 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/IconButton/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/IconButton/index.tsx
@@ -79,8 +79,12 @@ const IconButton: React.FC = ({
};
return (
+ // antd's Card renders a fixed
(no polymorphic tag support) with
+ // its own rich internal layout (cover/title/tooltip); that doesn't map
+ // onto a native
)}
{!isButton && (
+ // Generic wrapper used by 19+ callers passing arbitrary
+ // triggerNode content that relies on block-level