mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
## 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>
203 lines
7.1 KiB
TypeScript
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)
|
|
}}
|
|
/>
|
|
</>
|
|
)
|
|
}
|