feat(dropdown accessibility): Wrap dropdown triggers with buttons for accessibility (#32189)

This commit is contained in:
Mehmet Salih Yavuz 2025-02-11 14:09:35 +03:00 committed by GitHub
parent a78968c68e
commit 60bbd72028
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 63 additions and 41 deletions

View File

@ -873,7 +873,9 @@ const SqlEditor: FC<Props> = ({
dropdownRender={() => renderDropdown()} dropdownRender={() => renderDropdown()}
trigger={['click']} trigger={['click']}
> >
<Icons.MoreHoriz iconColor={theme.colors.grayscale.base} /> <Button buttonSize="xsmall" type="link" showMarginRight={false}>
<Icons.MoreHoriz iconColor={theme.colors.grayscale.base} />
</Button>
</Dropdown> </Dropdown>
</div> </div>
</> </>

View File

@ -30,7 +30,7 @@ import {
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
import type { SqlLabRootState } from 'src/SqlLab/types'; import type { SqlLabRootState } from 'src/SqlLab/types';
import { Skeleton, AntdBreadcrumb as Breadcrumb } from 'src/components'; import { Skeleton, AntdBreadcrumb as Breadcrumb, Button } from 'src/components';
import { Dropdown } from 'src/components/Dropdown'; import { Dropdown } from 'src/components/Dropdown';
import FilterableTable from 'src/components/FilterableTable'; import FilterableTable from 'src/components/FilterableTable';
import Tabs from 'src/components/Tabs'; import Tabs from 'src/components/Tabs';
@ -324,11 +324,13 @@ const TablePreview: FC<Props> = ({ dbId, catalog, schema, tableName }) => {
)} )}
trigger={['click']} trigger={['click']}
> >
<Icons.DownSquareOutlined <Button buttonSize="xsmall" type="link">
iconSize="m" <Icons.DownSquareOutlined
style={{ marginTop: 2, marginLeft: 4 }} iconSize="m"
aria-label={t('Table actions')} style={{ marginTop: 2, marginLeft: 4 }}
/> aria-label={t('Table actions')}
/>
</Button>
</Dropdown> </Dropdown>
</Title> </Title>
{isMetadataRefreshing ? ( {isMetadataRefreshing ? (

View File

@ -29,6 +29,7 @@ import { Menu } from 'src/components/Menu';
import FaveStar from 'src/components/FaveStar'; import FaveStar from 'src/components/FaveStar';
import FacePile from 'src/components/FacePile'; import FacePile from 'src/components/FacePile';
import { handleChartDelete, CardStyles } from 'src/views/CRUD/utils'; import { handleChartDelete, CardStyles } from 'src/views/CRUD/utils';
import Button from 'src/components/Button';
interface ChartCardProps { interface ChartCardProps {
chart: Chart; chart: Chart;
@ -172,8 +173,10 @@ export default function ChartCard({
isStarred={favoriteStatus} isStarred={favoriteStatus}
/> />
)} )}
<Dropdown dropdownRender={() => menu}> <Dropdown dropdownRender={() => menu} trigger={['click', 'hover']}>
<Icons.MoreVert iconColor={theme.colors.grayscale.base} /> <Button buttonSize="xsmall" type="link">
<Icons.MoreVert iconColor={theme.colors.grayscale.base} />
</Button>
</Dropdown> </Dropdown>
</ListViewCard.Actions> </ListViewCard.Actions>
} }

View File

@ -34,6 +34,7 @@ import { PublishedLabel } from 'src/components/Label';
import FacePile from 'src/components/FacePile'; import FacePile from 'src/components/FacePile';
import FaveStar from 'src/components/FaveStar'; import FaveStar from 'src/components/FaveStar';
import { Dashboard } from 'src/views/CRUD/types'; import { Dashboard } from 'src/views/CRUD/types';
import { Button } from 'src/components';
interface DashboardCardProps { interface DashboardCardProps {
isChart?: boolean; isChart?: boolean;
@ -179,8 +180,10 @@ function DashboardCard({
isStarred={favoriteStatus} isStarred={favoriteStatus}
/> />
)} )}
<Dropdown dropdownRender={() => menu}> <Dropdown dropdownRender={() => menu} trigger={['hover', 'click']}>
<Icons.MoreVert iconColor={theme.colors.grayscale.base} /> <Button buttonSize="xsmall" type="link">
<Icons.MoreVert iconColor={theme.colors.grayscale.base} />
</Button>
</Dropdown> </Dropdown>
</ListViewCard.Actions> </ListViewCard.Actions>
} }

View File

@ -104,7 +104,7 @@ describe('SavedQueries', () => {
it('renders a submenu with clickable tables and buttons', async () => { it('renders a submenu with clickable tables and buttons', async () => {
expect(wrapper.find(SubMenu)).toExist(); expect(wrapper.find(SubMenu)).toExist();
expect(wrapper.find('[role="tab"]')).toHaveLength(1); expect(wrapper.find('[role="tab"]')).toHaveLength(1);
expect(wrapper.find('button')).toHaveLength(2); expect(wrapper.find('button')).toHaveLength(5);
clickTab(0); clickTab(0);
await waitForComponentToPaint(wrapper); await waitForComponentToPaint(wrapper);
expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(2); expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(2);

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { useState } from 'react'; import { useCallback, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { styled, SupersetClient, t, useTheme } from '@superset-ui/core'; import { styled, SupersetClient, t, useTheme } from '@superset-ui/core';
import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light'; import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light';
@ -39,6 +39,7 @@ import {
PAGE_SIZE, PAGE_SIZE,
shortenSQL, shortenSQL,
} from 'src/views/CRUD/utils'; } from 'src/views/CRUD/utils';
import { Button } from 'src/components';
import SubMenu from './SubMenu'; import SubMenu from './SubMenu';
import EmptyState from './EmptyState'; import EmptyState from './EmptyState';
import { WelcomeTable } from './types'; import { WelcomeTable } from './types';
@ -191,33 +192,36 @@ const SavedQueries = ({
filters: getFilterValues(tab, WelcomeTable.SavedQueries, user), filters: getFilterValues(tab, WelcomeTable.SavedQueries, user),
}); });
const renderMenu = (query: Query) => ( const renderMenu = useCallback(
<Menu> (query: Query) => (
{canEdit && ( <Menu>
<Menu.Item> {canEdit && (
<Link to={`/sqllab?savedQueryId=${query.id}`}>{t('Edit')}</Link> <Menu.Item>
</Menu.Item> <Link to={`/sqllab?savedQueryId=${query.id}`}>{t('Edit')}</Link>
)} </Menu.Item>
<Menu.Item )}
onClick={() => {
if (query.id) {
copyQueryLink(query.id, addDangerToast, addSuccessToast);
}
}}
>
{t('Share')}
</Menu.Item>
{canDelete && (
<Menu.Item <Menu.Item
onClick={() => { onClick={() => {
setQueryDeleteModal(true); if (query.id) {
setCurrentlyEdited(query); copyQueryLink(query.id, addDangerToast, addSuccessToast);
}
}} }}
> >
{t('Delete')} {t('Share')}
</Menu.Item> </Menu.Item>
)} {canDelete && (
</Menu> <Menu.Item
onClick={() => {
setQueryDeleteModal(true);
setCurrentlyEdited(query);
}}
>
{t('Delete')}
</Menu.Item>
)}
</Menu>
),
[],
); );
if (loading) return <LoadingCards cover={showThumbnails} />; if (loading) return <LoadingCards cover={showThumbnails} />;
@ -315,10 +319,15 @@ const SavedQueries = ({
e.preventDefault(); e.preventDefault();
}} }}
> >
<Dropdown dropdownRender={() => renderMenu(q)}> <Dropdown
<Icons.MoreVert dropdownRender={() => renderMenu(q)}
iconColor={theme.colors.grayscale.base} trigger={['click', 'hover']}
/> >
<Button buttonSize="xsmall" type="link">
<Icons.MoreVert
iconColor={theme.colors.grayscale.base}
/>
</Button>
</Dropdown> </Dropdown>
</ListViewCard.Actions> </ListViewCard.Actions>
</QueryData> </QueryData>

View File

@ -26,6 +26,7 @@ import ListViewCard from 'src/components/ListViewCard';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
import { Tag } from 'src/views/CRUD/types'; import { Tag } from 'src/views/CRUD/types';
import { deleteTags } from 'src/features/tags/tags'; import { deleteTags } from 'src/features/tags/tags';
import { Button } from 'src/components';
interface TagCardProps { interface TagCardProps {
tag: Tag; tag: Tag;
@ -108,8 +109,10 @@ function TagCard({
e.preventDefault(); e.preventDefault();
}} }}
> >
<Dropdown dropdownRender={() => menu}> <Dropdown dropdownRender={() => menu} trigger={['click', 'hover']}>
<Icons.MoreVert iconColor={theme.colors.grayscale.base} /> <Button buttonSize="xsmall" type="link">
<Icons.MoreVert iconColor={theme.colors.grayscale.base} />
</Button>
</Dropdown> </Dropdown>
</ListViewCard.Actions> </ListViewCard.Actions>
} }