From 2dd75cbfdd4dd83311056bf4a75386f7f7a726fe Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Wed, 18 Feb 2026 09:41:10 -0700 Subject: [PATCH] chore: Update aria-controls and aria-expanded for components (#42961) ## 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? - React Doctor fixes for aria controls and aria expanded - Updated eslint to include the role --- .../ThirdPartyAuthForm/AwsRegionSelector.tsx | 13 ++++++---- .../NewPaymentMethodElement.tsx | 21 ++++++++++----- .../Backups/PITR/TimezoneSelection.tsx | 12 +++++---- .../Wrappers/WrapperTableEditor.tsx | 11 +++++--- .../BillingCustomerDataForm.tsx | 26 +++++++++++++++---- .../interfaces/Organization/Usage/Usage.tsx | 12 +++++---- .../interfaces/Support/ProjectAndPlanInfo.tsx | 17 +++++++----- .../ColumnEditor/ColumnType.tsx | 21 ++++++++++----- .../ui/DataTable/DataTableViewOptions.tsx | 8 +++--- .../components/ui/EditorPanel/EditorPanel.tsx | 6 +++-- .../ui/OrganizationProjectSelector.tsx | 17 ++++++++++-- apps/studio/eslint.config.cjs | 3 +++ .../pages/project/[ref]/functions/new.tsx | 24 +++++++++-------- 13 files changed, 128 insertions(+), 63 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx index ec0a1d18ee7..1af9df53dde 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AwsRegionSelector.tsx @@ -1,19 +1,19 @@ import { Check, ChevronsUpDown } from 'lucide-react' -import { useState } from 'react' +import { useId, useState } from 'react' import { Button, + cn, + Command_Shadcn_, CommandEmpty_Shadcn_, CommandGroup_Shadcn_, CommandInput_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_, - Command_Shadcn_, FormControl_Shadcn_, + Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, - Popover_Shadcn_, ScrollArea, - cn, } from 'ui' // copied from https://docs.aws.amazon.com/general/latest/gr/cognito_identity.html @@ -56,6 +56,7 @@ export const AwsRegionSelector = ({ onChange: (value: string) => void }) => { const [open, setOpen] = useState(false) + const listboxId = useId() return ( @@ -64,6 +65,8 @@ export const AwsRegionSelector = ({ - + diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx index d527da4b6d4..8f7a3c94a3b 100644 --- a/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx +++ b/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx @@ -1,18 +1,18 @@ import { CheckIcon, ChevronsUpDown, Globe } from 'lucide-react' -import { useState } from 'react' +import { useId, useState } from 'react' import { Button, + cn, + Command_Shadcn_, CommandEmpty_Shadcn_, CommandGroup_Shadcn_, CommandInput_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_, - Command_Shadcn_, + Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, - Popover_Shadcn_, ScrollArea, - cn, } from 'ui' import { ALL_TIMEZONES } from './PITR.constants' @@ -28,6 +28,7 @@ export const TimezoneSelection = ({ onSelectTimezone, }: TimezoneSelectionProps) => { const [open, setOpen] = useState(false) + const listboxId = useId() const timezoneOptions = ALL_TIMEZONES.map((option) => option.text) @@ -38,6 +39,7 @@ export const TimezoneSelection = ({ - + diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx index 56849026784..95ac87a2d18 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx @@ -1,9 +1,8 @@ -import { Check, ChevronsUpDown, Database, Plus } from 'lucide-react' -import { useEffect, useState } from 'react' - import { ActionBar } from 'components/interfaces/TableGridEditor/SidePanelEditor/ActionBar' import { useSchemasQuery } from 'data/database/schemas-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' +import { Check, ChevronsUpDown, Database, Plus } from 'lucide-react' +import { useEffect, useId, useState } from 'react' import { Button, cn, @@ -25,6 +24,7 @@ import { SidePanel, } from 'ui' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' + import WrapperDynamicColumns from './WrapperDynamicColumns' import type { Table, TableOption } from './Wrappers.types' import { makeValidateRequired } from './Wrappers.utils' @@ -48,6 +48,7 @@ const WrapperTableEditor = ({ initialData, }: WrapperTableEditorProps) => { const [open, setOpen] = useState(false) + const listboxId = useId() const [selectedTableIndex, setSelectedTableIndex] = useState('') useEffect(() => { @@ -101,6 +102,8 @@ const WrapperTableEditor = ({ - + @@ -246,6 +255,8 @@ export const BillingCustomerDataForm = ({ role="combobox" size="medium" disabled={disabled} + aria-expanded={showTaxIDsPopover} + aria-controls={taxIdListboxId} className={cn( 'w-full justify-between h-[34px] pr-2', !selectedTaxId && 'text-muted' @@ -260,7 +271,12 @@ export const BillingCustomerDataForm = ({ - + diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index c6e1c0867dd..6e8baf4aa65 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -1,8 +1,4 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import dayjs from 'dayjs' -import Link from 'next/link' -import { useMemo, useState } from 'react' - import { useParams } from 'common' import { ScaffoldContainer, @@ -17,13 +13,17 @@ import { OrganizationProjectSelector } from 'components/ui/OrganizationProjectSe import { useOrgDailyStatsQuery } from 'data/analytics/org-daily-stats-query' import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' +import dayjs from 'dayjs' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants/metrics' import { Check, ChevronDown } from 'lucide-react' +import Link from 'next/link' import { useQueryState } from 'nuqs' +import { useMemo, useState } from 'react' import { Button, cn, CommandGroup_Shadcn_, CommandItem_Shadcn_ } from 'ui' import { Admonition } from 'ui-patterns' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' + import { Restriction } from '../BillingSettings/Restriction' import ActiveCompute from './ActiveCompute' import Activity from './Activity' @@ -163,13 +163,15 @@ export const Usage = () => { onSelect={(project) => { setSelectedProjectRef(project.ref) }} - renderTrigger={() => { + renderTrigger={({ listboxId, open }) => { return ( - + } tooltip={{ content: { side: 'bottom', text: 'Toggle column visibility' } }} /> - + diff --git a/apps/studio/components/ui/EditorPanel/EditorPanel.tsx b/apps/studio/components/ui/EditorPanel/EditorPanel.tsx index a31d9fe0359..7f660ea02c1 100644 --- a/apps/studio/components/ui/EditorPanel/EditorPanel.tsx +++ b/apps/studio/components/ui/EditorPanel/EditorPanel.tsx @@ -16,7 +16,7 @@ import { BASE_PATH } from 'lib/constants' import { useProfile } from 'lib/profile' import { Book, Maximize2, X } from 'lucide-react' import { useRouter } from 'next/router' -import { useState } from 'react' +import { useId, useState } from 'react' import { useEditorPanelStateSnapshot } from 'state/editor-panel-state' import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' @@ -83,6 +83,7 @@ export const EditorPanel = () => { const [showWarning, setShowWarning] = useState<'hasWriteOperation' | 'hasUnknownFunctions'>() const [showResults, setShowResults] = useState(true) const [isTemplatesOpen, setIsTemplatesOpen] = useState(false) + const templatesListboxId = useId() const errorHeader = error?.formattedError?.split('\n')?.filter((x: string) => x.length > 0)?.[0] const errorContent = @@ -167,12 +168,13 @@ export const EditorPanel = () => { role="combobox" className="mr-2" aria-expanded={isTemplatesOpen} + aria-controls={templatesListboxId} icon={} > Templates - + diff --git a/apps/studio/components/ui/OrganizationProjectSelector.tsx b/apps/studio/components/ui/OrganizationProjectSelector.tsx index f53cfa2bfbf..d030f245549 100644 --- a/apps/studio/components/ui/OrganizationProjectSelector.tsx +++ b/apps/studio/components/ui/OrganizationProjectSelector.tsx @@ -3,7 +3,7 @@ import { useDebounce, useIntersectionObserver } from '@uidotdev/usehooks' import { OrgProject, useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Check, ChevronsUpDown, HelpCircle } from 'lucide-react' -import { ReactNode, useEffect, useMemo, useRef, useState } from 'react' +import { ReactNode, useEffect, useId, useMemo, useRef, useState } from 'react' import { Button, cn, @@ -34,9 +34,13 @@ interface OrganizationProjectSelectorSelectorProps { renderTrigger?: ({ isLoading, project, + listboxId, + open, }: { isLoading: boolean project?: OrgProject + listboxId: string + open: boolean }) => ReactNode renderActions?: (setOpen: (value: boolean) => void) => ReactNode onSelect?: (project: OrgProject) => void @@ -69,6 +73,7 @@ export const OrganizationProjectSelector = ({ const [openInternal, setOpenInternal] = useState(false) const open = _open ?? openInternal const setOpen = _setOpen ?? setOpenInternal + const listboxId = useId() const [search, setSearch] = useState('') const debouncedSearch = useDebounce(search, 500) @@ -130,13 +135,20 @@ export const OrganizationProjectSelector = ({ {renderTrigger ? ( - renderTrigger({ isLoading: isLoadingProjects || isFetching, project: selectedProject }) + renderTrigger({ + isLoading: isLoadingProjects || isFetching, + project: selectedProject, + listboxId, + open, + }) ) : ( - +