fix: update loading state for filter bars (#42457)

## 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?

Update loading state for filter bars when using the new experience

## Demo


https://github.com/user-attachments/assets/a243a6b2-2060-47b3-9c6c-6e9f7ceb1eb7


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

* **New Features**
* Added a loading indicator in the filter UI that displays a spinning
icon when data is being refetched.
* Header now surfaces refetching state to the filter controls so the
loading indicator appears during refreshes.
* Filter bar supports a customizable icon prop to enable the new loading
overlay behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ali Waseem authored and GitHub committed 2026-02-04 14:20:53 -07:00
1 parent 1696262088
commit 48d894243f
4 files changed
+52 -22

No files matched your search

@@ -30,19 +30,19 @@ import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import {
Button,
cn,
copyToClipboard,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Separator,
cn,
copyToClipboard,
} from 'ui'
import { useInitializeFiltersFromUrl, useSyncFiltersToUrl } from '../../hooks/useFilterLifeCycle'
import { ExportDialog } from './ExportDialog'
import { formatRowsForCSV } from './Header.utils'
import { FilterPopoverNew } from './filter/FilterPopoverNew'
import { formatRowsForCSV } from './Header.utils'
import { SortPopover } from './sort/SortPopover'
export type HeaderProps = {
@@ -71,7 +71,7 @@ export const HeaderNew = ({
) : snap.selectedRows.size > 0 ? (
<RowHeader tableQueriesEnabled={tableQueriesEnabled} />
) : (
<DefaultHeader tableQueriesEnabled={tableQueriesEnabled} />
<DefaultHeader tableQueriesEnabled={tableQueriesEnabled} isRefetching={isRefetching} />
)}
<div className="flex items-center gap-2">
<GridHeaderActions table={snap.originalTable} isRefetching={isRefetching} />
@@ -84,11 +84,12 @@ export const HeaderNew = ({
const DefaultHeader = ({
tableQueriesEnabled = true,
}: Pick<HeaderProps, 'tableQueriesEnabled'>) => {
isRefetching,
}: Pick<HeaderProps, 'tableQueriesEnabled' | 'isRefetching'>) => {
return (
<>
<div className="flex-1 min-w-0">
<FilterPopoverNew />
<div className="flex-1 min-w-0 flex items-center gap-2">
<FilterPopoverNew isRefetching={isRefetching} />
</div>
<SortPopover tableQueriesEnabled={tableQueriesEnabled} />
</>
@@ -2,6 +2,7 @@ import { useTableFilterNew } from 'components/grid/hooks/useTableFilterNew'
import type { Filter } from 'components/grid/types'
import { useSqlFilterGenerateMutation } from 'data/ai/sql-filter-mutation'
import { format } from 'date-fns'
import { Loader2 } from 'lucide-react'
import { useCallback, useMemo, useState } from 'react'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import { Button, Calendar } from 'ui'
@@ -20,6 +21,7 @@ import { columnToFilterProperty } from './FilterPopoverNew.utils'
export interface FilterPopoverProps {
portal?: boolean
isRefetching?: boolean
}
// Convert Filter[] to FilterGroup
@@ -114,7 +116,7 @@ function serializeFilterProperties(
}))
}
export const FilterPopoverNew = ({ portal = true }: FilterPopoverProps) => {
export const FilterPopoverNew = ({ isRefetching = false }: FilterPopoverProps) => {
const { filters, setFilters } = useTableFilterNew()
const snap = useTableEditorTableStateSnapshot()
@@ -188,17 +190,24 @@ export const FilterPopoverNew = ({ portal = true }: FilterPopoverProps) => {
[generateFilters, serializableFilterProperties, handleFilterChange, setFreeformText]
)
const icon = isRefetching ? (
<Loader2 className="animate-spin text-brand h-4 w-4 shrink-0" aria-label="Loading table data" />
) : null
return (
<FilterBar
filterProperties={filterProperties}
filters={filterGroup}
onFilterChange={handleFilterChange}
freeformText={freeformText}
onFreeformTextChange={setFreeformText}
actions={actions}
isLoading={isGenerating}
variant="pill"
className="bg-transparent border-0"
/>
<div className="flex-1 min-w-0">
<FilterBar
filterProperties={filterProperties}
filters={filterGroup}
onFilterChange={handleFilterChange}
freeformText={freeformText}
onFreeformTextChange={setFreeformText}
actions={actions}
isLoading={isGenerating}
variant="pill"
className="bg-transparent border-0"
icon={icon}
/>
</div>
)
}
@@ -2,7 +2,9 @@
import { motion } from 'framer-motion'
import { Search } from 'lucide-react'
import React from 'react'
import { cn } from 'ui'
import { FilterBarRoot, useFilterBar, type FilterBarVariant } from './FilterBarContext'
import { FilterGroup } from './FilterGroup'
import { FilterBarAction, FilterGroup as FilterGroupType, FilterProperty } from './types'
@@ -18,10 +20,11 @@ export type FilterBarProps = {
className?: string
supportsOperators?: boolean
variant?: FilterBarVariant
icon?: React.ReactNode
}
function FilterBarContent({ className }: { className?: string }) {
const { filters, error, optionsError, isLoading, variant } = useFilterBar()
const { filters, error, optionsError, isLoading, variant, icon: loadingIcon } = useFilterBar()
return (
<div className="w-full space-y-2 relative">
@@ -33,11 +36,21 @@ function FilterBarContent({ className }: { className?: string }) {
>
<div
className={cn(
'flex items-center shrink-0 px-2 bg-surface-200',
'relative flex items-center justify-center shrink-0 px-2 bg-surface-200',
variant === 'pill' ? 'bg-transparent border-r-0' : 'border-r'
)}
>
<Search className="text-foreground-muted w-4 h-4 sticky" />
<div
className={cn(
'transition-opacity duration-300 ease-in-out',
loadingIcon ? 'opacity-0' : 'opacity-100'
)}
>
<Search className="text-foreground-muted w-4 h-4 sticky" />
</div>
{loadingIcon && (
<div className="absolute inset-0 flex items-center justify-center">{loadingIcon}</div>
)}
</div>
<motion.div
className="flex-1 flex flex-wrap items-stretch gap-0"
@@ -97,6 +110,7 @@ export function FilterBar({
className,
supportsOperators = false,
variant = 'default',
icon,
}: FilterBarProps) {
return (
<FilterBarRoot
@@ -109,6 +123,7 @@ export function FilterBar({
isLoading={isLoading}
supportsOperators={supportsOperators}
variant={variant}
icon={icon}
>
<FilterBarContent className={className} />
</FilterBarRoot>
@@ -1,6 +1,7 @@
'use client'
import React, { createContext, useCallback, useContext, useEffect, useRef } from 'react'
import { ActiveInput, useFilterBarState, useOptionsCache } from './hooks'
import { MenuItem } from './menuItems'
import { FilterBarAction, FilterGroup, FilterOptionObject, FilterProperty } from './types'
@@ -54,6 +55,7 @@ export type FilterBarContextValue = {
supportsOperators: boolean
variant: FilterBarVariant
actions?: FilterBarAction[]
icon?: React.ReactNode
// Refs
rootRef: React.RefObject<HTMLDivElement>
@@ -80,6 +82,7 @@ export type FilterBarRootProps = {
isLoading?: boolean
supportsOperators?: boolean
variant?: FilterBarVariant
icon?: React.ReactNode
}
export type FilterBarVariant = 'default' | 'pill'
@@ -95,6 +98,7 @@ export function FilterBarRoot({
isLoading: externalLoading,
supportsOperators = false,
variant = 'default',
icon,
}: FilterBarRootProps) {
const rootRef = useRef<HTMLDivElement>(null)
@@ -306,6 +310,7 @@ export function FilterBarRoot({
supportsOperators,
variant,
actions,
icon,
// Refs
rootRef,