mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
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:
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,
|
||||
|
||||
Reference in new issue
Block a user