Files
ChloeGarciaMillerandandGildas Garcia ef7b2a5211 fix: ESLint errors relating to accessibility in integration section (#50224)
## 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?

Improving accessibility by adding aria-label.
Adding Tooltip for icon only buttons.

## What is the current behavior?

Some `aria-label` and `Tooltip` are missing.

## What is the new behavior?

Icon-only buttons have now Tooltip and aria-label have been added.

## Additional context

No visual changes have been made.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Accessibility Improvements**
* Added descriptive tooltips and screen-reader labels to search-clear
and close-panel controls.
* Added accessible labels and tooltips for managing secrets, showing or
hiding secret values, and editing or removing foreign tables.
* Improved the lint details panel close control with a descriptive
label.
* Prevented duplicate tooltip announcements for assistive technologies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
2026-09-22 11:31:30 +02:00

203 lines
7.1 KiB
TypeScript

import { useParams } from 'common'
import { RefreshCw, Search, X } from 'lucide-react'
import { useRouter } from 'next/router'
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
import { useMemo, useRef, useState } from 'react'
import DataGrid, { Row } from 'react-data-grid'
import { Button, cn, LoadingLine, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { CreateQueueSheet } from './CreateQueueSheet'
import { formatQueueColumns, prepareQueuesForDataGrid } from './Queues.utils'
import { AlertError } from '@/components/ui/AlertError'
import { useQueuesQuery } from '@/data/database-queues/database-queues-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { onSearchInputEscape } from '@/lib/keyboard'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
export const QueuesTab = () => {
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const [searchQuery, setSearchQuery] = useQueryState('search', parseAsString.withDefault(''))
const [search, setSearch] = useState(searchQuery)
const [createQueueSheetShown, setCreateQueueSheetShown] = useQueryState(
'new',
parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
)
const searchInputRef = useRef<HTMLInputElement>(null)
const clearSearch = () => {
setSearch('')
setSearchQuery('')
}
useShortcut(
SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH,
() => {
searchInputRef.current?.focus()
searchInputRef.current?.select()
},
{ label: 'Search queues' }
)
useShortcut(SHORTCUT_IDS.LIST_PAGE_RESET_FILTERS, clearSearch)
useShortcut(SHORTCUT_IDS.LIST_PAGE_NEW_ITEM, () => setCreateQueueSheetShown(true), {
label: 'Create queue',
})
const {
data: queues,
error,
isPending: isLoading,
isError,
isRefetching,
refetch,
} = useQueuesQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
// Filter queues based on search query
const filteredQueues = useMemo(() => {
if (!queues) return []
if (!searchQuery) return queues
return queues.filter((queue) =>
queue.queue_name.toLowerCase().includes(searchQuery.toLowerCase())
)
}, [queues, searchQuery])
// Prepare queues for DataGrid
const queueData = useMemo(() => prepareQueuesForDataGrid(filteredQueues), [filteredQueues])
// Get columns configuration
const columns = useMemo(() => formatQueueColumns(), [])
return (
<>
<div className="h-full w-full space-y-4">
<div className="h-full w-full flex flex-col relative">
<div className="bg-surface-200 py-3 px-10 flex items-center justify-between flex-wrap">
<Input
ref={searchInputRef}
size="tiny"
className="w-52"
placeholder="Search for a queue"
icon={<Search />}
value={search ?? ''}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => {
onSearchInputEscape(search ?? '', clearSearch)(e)
if (e.code === 'Enter' || e.code === 'NumpadEnter') setSearchQuery(search.trim())
}}
actions={[
search && (
<Tooltip>
<TooltipTrigger asChild>
<Button
key="clear"
size="tiny"
variant="text"
icon={<X />}
onClick={clearSearch}
className="p-0 h-5 w-5"
aria-label="Clear search"
// Tooltip repeats the label; screen readers would read it twice
aria-describedby={undefined}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Clear search</TooltipContent>
</Tooltip>
),
]}
/>
<div className="flex items-center gap-x-2">
<Button icon={<RefreshCw />} loading={isRefetching} onClick={() => refetch()}>
Refresh
</Button>
<Button variant="primary" onClick={() => setCreateQueueSheetShown(true)}>
Create queue
</Button>
</div>
</div>
<LoadingLine loading={isLoading || isRefetching} />
<DataGrid
className="grow border-t-0! border-b-0!"
rowHeight={44}
headerRowHeight={36}
columns={columns}
rows={queueData}
rowKeyGetter={(row) => row.id}
rowClass={() => {
return cn(
'cursor-pointer',
'[&>.rdg-cell]:border-box [&>.rdg-cell]:outline-hidden [&>.rdg-cell]:shadow-none',
'[&>.rdg-cell:first-child>div]:ml-8'
)
}}
renderers={{
renderRow(_, props) {
return (
<Row
key={props.row.queue_name}
{...props}
onClick={() => {
const { queue_name } = props.row
const url = `/project/${ref}/integrations/queues/queues/${queue_name}`
router.push(url)
}}
/>
)
},
}}
/>
{/* Render 0 rows state outside of the grid */}
{queueData.length === 0 ? (
isLoading ? (
<div className="absolute top-28 px-10 w-full">
<GenericSkeletonLoader />
</div>
) : isError ? (
<div className="absolute top-28 px-10 flex flex-col items-center justify-center w-full">
<AlertError subject="Failed to retrieve database queues" error={error} />
</div>
) : (
<div className="absolute top-32 px-6 w-full">
<div className="text-center text-sm flex flex-col gap-y-1">
<p className="text-foreground">
{!!searchQuery ? 'No queues found' : 'No queues created yet'}
</p>
<p className="text-foreground-light">
{!!searchQuery
? 'There are currently no queues based on the search applied'
: 'There are currently no queues created yet in your project'}
</p>
</div>
</div>
)
) : null}
<div className="flex justify-between min-h-9 h-9 overflow-hidden items-center px-6 w-full border-t text-xs text-foreground-light">
{`Total: ${queueData.length} queues`}
</div>
</div>
</div>
<CreateQueueSheet
visible={createQueueSheetShown}
onClose={() => {
setCreateQueueSheetShown(false)
}}
/>
</>
)
}