mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
Fix: improve accessibility for icon buttons (database menu) (#47531)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Bug fix (accessibility improvement) ## What is the current behavior? Icon-only buttons do not have explicit accessible names for screen readers or tooltips. ## What is the new behavior? All icon-only buttons now have explicit accessible names using visually hidden text (sr-only), ensuring proper screen reader support. ## Additional context Tooltip text is preserved or added for visual users. No visual changes were introduced. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Tightened end-to-end checks for policy creation so policy names must match exactly in the list. * Improved validation coverage for SELECT, INSERT, UPDATE, and DELETE policy flows, reducing the chance of false-positive test matches. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
623a9230bd
commit
c87f673c4c
5 files changed
+62
-35
No files matched your search
@@ -14,6 +14,9 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
@@ -264,13 +267,18 @@ export const Indexes = () => {
|
||||
View definition
|
||||
</Button>
|
||||
{!isSchemaLocked && (
|
||||
<Button
|
||||
aria-label="Delete index"
|
||||
variant="text"
|
||||
className="px-1"
|
||||
icon={<Trash />}
|
||||
onClick={() => setDeleteIndexId(index.name)}
|
||||
/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
aria-label="Delete index"
|
||||
variant="text"
|
||||
className="px-1"
|
||||
icon={<Trash />}
|
||||
onClick={() => setDeleteIndexId(index.name)}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">Delete index</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
@@ -121,14 +121,20 @@ export const PolicyRow = ({
|
||||
<TableCell className="text-right whitespace-nowrap">
|
||||
{!isLocked && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="px-1.5"
|
||||
icon={<MoreVertical />}
|
||||
data-testid={`policy-${policy.name}-actions-button`}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="px-1.5"
|
||||
icon={<MoreVertical />}
|
||||
data-testid={`policy-${policy.name}-actions-button`}
|
||||
aria-label={`${policy.name} actions`}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">More options</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-52">
|
||||
<DropdownMenuItem className="gap-x-2" onClick={() => onSelectEditPolicy(policy)}>
|
||||
<Edit size={14} />
|
||||
|
||||
@@ -17,6 +17,9 @@ import {
|
||||
FormControl,
|
||||
FormField,
|
||||
Switch,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import * as z from 'zod'
|
||||
@@ -154,14 +157,19 @@ export const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps
|
||||
</CollapsibleTrigger>
|
||||
{!disabled && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="px-1"
|
||||
icon={<MoreVertical />}
|
||||
aria-label={`${role.name} actions`}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="px-1"
|
||||
icon={<MoreVertical />}
|
||||
aria-label={`${role.name} actions`}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">More options</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-[120px]">
|
||||
<DropdownMenuItem
|
||||
className="space-x-2"
|
||||
|
||||
@@ -514,14 +514,19 @@ export const TableList = ({
|
||||
|
||||
{!isSchemaLocked && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="px-1"
|
||||
icon={<MoreVertical />}
|
||||
aria-label={`Table ${x.name} actions`}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="px-1"
|
||||
icon={<MoreVertical />}
|
||||
aria-label={`Table ${x.name} actions`}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">More options</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-40">
|
||||
<DropdownMenuItem
|
||||
className="flex items-center space-x-2"
|
||||
|
||||
@@ -231,7 +231,7 @@ test.describe('RLS Policies', () => {
|
||||
).toBeVisible({ timeout: 50000 })
|
||||
|
||||
// Verify policy appears in the list
|
||||
await expect(page.getByRole('button', { name: policySelectName })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: policySelectName, exact: true })).toBeVisible()
|
||||
|
||||
// Verify policy details
|
||||
const policyRow = page.locator(`tr:has-text("${policySelectName}")`)
|
||||
@@ -288,7 +288,7 @@ test.describe('RLS Policies', () => {
|
||||
})
|
||||
|
||||
// Verify policy appears with correct details
|
||||
await expect(page.getByRole('button', { name: policyInsertName })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: policyInsertName, exact: true })).toBeVisible()
|
||||
const policyRow = page.locator(`tr:has-text("${policyInsertName}")`)
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^INSERT$/ })).toBeVisible()
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^authenticated$/ })).toBeVisible()
|
||||
@@ -349,7 +349,7 @@ test.describe('RLS Policies', () => {
|
||||
})
|
||||
|
||||
// Verify policy appears
|
||||
await expect(page.getByRole('button', { name: policyUpdateName })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: policyUpdateName, exact: true })).toBeVisible()
|
||||
const policyRow = page.locator(`tr:has-text("${policyUpdateName}")`)
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^UPDATE$/ })).toBeVisible()
|
||||
})
|
||||
@@ -401,7 +401,7 @@ test.describe('RLS Policies', () => {
|
||||
})
|
||||
|
||||
// Verify policy appears
|
||||
await expect(page.getByRole('button', { name: policyDeleteName })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: policyDeleteName, exact: true })).toBeVisible()
|
||||
const policyRow = page.locator(`tr:has-text("${policyDeleteName}")`)
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^DELETE$/ })).toBeVisible()
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user