mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Chore/update filter sort table editor to new popover (#25832)
* Update table editor FilterPopover to new popover component * Update table editor SortPopover to new popover component * Don't close popovers * Address feedback
This commit is contained in:
1 parent
d025e0f739
commit
88bbe7042a
6 files changed
+112
-106
No files matched your search
@@ -1,11 +1,18 @@
|
||||
import { useUrlState } from 'hooks'
|
||||
import update from 'immutability-helper'
|
||||
import { isEqual } from 'lodash'
|
||||
import { FilterIcon, Plus } from 'lucide-react'
|
||||
import { KeyboardEvent, useCallback, useMemo, useState } from 'react'
|
||||
import { Button, IconFilter, IconPlus, Popover } from 'ui'
|
||||
|
||||
import { formatFilterURLParams } from 'components/grid/SupabaseGrid.utils'
|
||||
import type { Filter, SupaTable } from 'components/grid/types'
|
||||
import {
|
||||
Button,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverSeparator_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
} from 'ui'
|
||||
import { FilterOperatorOptions } from './Filter.constants'
|
||||
import FilterRow from './FilterRow'
|
||||
|
||||
@@ -16,38 +23,54 @@ export interface FilterPopoverProps {
|
||||
}
|
||||
|
||||
const FilterPopover = ({ table, filters, setParams }: FilterPopoverProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const btnText =
|
||||
(filters || []).length > 0
|
||||
? `Filtered by ${filters.length} rule${filters.length > 1 ? 's' : ''}`
|
||||
: 'Filter'
|
||||
|
||||
const onApplyFilters = (appliedFilters: Filter[]) => {
|
||||
setParams((prevParams) => {
|
||||
return {
|
||||
...prevParams,
|
||||
filter: appliedFilters.map((filter) => {
|
||||
const selectedOperator = FilterOperatorOptions.find(
|
||||
(option) => option.value === filter.operator
|
||||
)
|
||||
|
||||
return `${filter.column}:${selectedOperator?.abbrev}:${filter.value}`
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
size="large"
|
||||
align="start"
|
||||
className="sb-grid-filter-popover"
|
||||
overlay={<FilterOverlay table={table} filters={filters} setParams={setParams} />}
|
||||
>
|
||||
<Button
|
||||
asChild
|
||||
type={(filters || []).length > 0 ? 'link' : 'text'}
|
||||
icon={
|
||||
<div className="text-foreground-light">
|
||||
<IconFilter strokeWidth={1.5} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span>{btnText}</span>
|
||||
</Button>
|
||||
</Popover>
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type={(filters || []).length > 0 ? 'link' : 'text'}
|
||||
icon={<FilterIcon strokeWidth={1.5} className="text-foreground-light" />}
|
||||
>
|
||||
{btnText}
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-96" side="bottom" align="start">
|
||||
<FilterOverlay table={table} filters={filters} onApplyFilters={onApplyFilters} />
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
export default FilterPopover
|
||||
|
||||
export interface FilterOverlayProps extends FilterPopoverProps {}
|
||||
interface FilterOverlayProps {
|
||||
table: SupaTable
|
||||
filters: string[]
|
||||
onApplyFilters: (filter: Filter[]) => void
|
||||
}
|
||||
|
||||
const FilterOverlay = ({ table, filters: filtersFromUrl, setParams }: FilterOverlayProps) => {
|
||||
const FilterOverlay = ({ table, filters: filtersFromUrl, onApplyFilters }: FilterOverlayProps) => {
|
||||
const initialFilters = useMemo(
|
||||
() => formatFilterURLParams((filtersFromUrl as string[]) ?? []),
|
||||
[filtersFromUrl]
|
||||
@@ -87,25 +110,8 @@ const FilterOverlay = ({ table, filters: filtersFromUrl, setParams }: FilterOver
|
||||
)
|
||||
}, [])
|
||||
|
||||
function onApplyFilter() {
|
||||
setParams((prevParams) => {
|
||||
return {
|
||||
...prevParams,
|
||||
filter: filters.map((filter) => {
|
||||
const selectedOperator = FilterOperatorOptions.find(
|
||||
(option) => option.value === filter.operator
|
||||
)
|
||||
|
||||
return `${filter.column}:${selectedOperator?.abbrev}:${filter.value}`
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleEnterKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||
if (event.key === 'Enter') {
|
||||
onApplyFilter()
|
||||
}
|
||||
if (event.key === 'Enter') onApplyFilters(filters)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -129,12 +135,16 @@ const FilterOverlay = ({ table, filters: filtersFromUrl, setParams }: FilterOver
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Popover.Separator />
|
||||
<PopoverSeparator_Shadcn_ />
|
||||
<div className="px-3 flex flex-row justify-between">
|
||||
<Button icon={<IconPlus />} type="text" onClick={onAddFilter}>
|
||||
<Button icon={<Plus />} type="text" onClick={onAddFilter}>
|
||||
Add filter
|
||||
</Button>
|
||||
<Button disabled={isEqual(filters, initialFilters)} type="default" onClick={onApplyFilter}>
|
||||
<Button
|
||||
disabled={isEqual(filters, initialFilters)}
|
||||
type="default"
|
||||
onClick={() => onApplyFilters(filters)}
|
||||
>
|
||||
Apply filter
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button, IconChevronDown, IconX, Input } from 'ui'
|
||||
import { DropdownControl } from 'components/grid/components/common'
|
||||
import type { Filter, FilterOperator, SupaTable } from 'components/grid/types'
|
||||
import { FilterOperatorOptions } from './Filter.constants'
|
||||
import { X } from 'lucide-react'
|
||||
|
||||
export interface FilterRowProps {
|
||||
table: SupaTable
|
||||
@@ -29,7 +30,7 @@ const FilterRow = ({ table, filter, filterIdx, onChange, onDelete, onKeyDown }:
|
||||
: 'Enter a value'
|
||||
|
||||
return (
|
||||
<div className="sb-grid-filter-row px-3">
|
||||
<div className="flex w-full items-center justify-between gap-x-1 px-3">
|
||||
<DropdownControl
|
||||
align="start"
|
||||
options={columnOptions}
|
||||
@@ -84,9 +85,9 @@ const FilterRow = ({ table, filter, filterIdx, onChange, onDelete, onKeyDown }:
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconX strokeWidth={1.5} size={14} />}
|
||||
size="tiny"
|
||||
type="text"
|
||||
className="px-1"
|
||||
icon={<X strokeWidth={1.5} size={14} />}
|
||||
onClick={() => onDelete(filterIdx)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { useUrlState } from 'hooks'
|
||||
import update from 'immutability-helper'
|
||||
import { isEqual } from 'lodash'
|
||||
import { ChevronDown, List } from 'lucide-react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { Button, IconChevronDown, IconList, Popover } from 'ui'
|
||||
|
||||
import { DropdownControl } from 'components/grid/components/common'
|
||||
import { formatSortURLParams } from 'components/grid/SupabaseGrid.utils'
|
||||
import { DropdownControl } from 'components/grid/components/common'
|
||||
import type { Sort, SupaTable } from 'components/grid/types'
|
||||
import {
|
||||
Button,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverSeparator_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
} from 'ui'
|
||||
import SortRow from './SortRow'
|
||||
|
||||
export interface SortPopoverProps {
|
||||
@@ -16,38 +23,48 @@ export interface SortPopoverProps {
|
||||
}
|
||||
|
||||
const SortPopover = ({ table, sorts, setParams }: SortPopoverProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const btnText =
|
||||
(sorts || []).length > 0
|
||||
? `Sorted by ${sorts.length} rule${sorts.length > 1 ? 's' : ''}`
|
||||
: 'Sort'
|
||||
|
||||
const onApplySorts = (appliedSorts: Sort[]) => {
|
||||
setParams((prevParams) => {
|
||||
return {
|
||||
...prevParams,
|
||||
sort: appliedSorts.map((sort) => `${sort.column}:${sort.ascending ? 'asc' : 'desc'}`),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
size="large"
|
||||
align="start"
|
||||
className="sb-grid-sort-popover"
|
||||
overlay={<SortOverlay table={table} sorts={sorts} setParams={setParams} />}
|
||||
>
|
||||
<Button
|
||||
asChild
|
||||
type={(sorts || []).length > 0 ? 'link' : 'text'}
|
||||
icon={
|
||||
<div className="text-foreground-light">
|
||||
<IconList strokeWidth={1.5} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span>{btnText}</span>
|
||||
</Button>
|
||||
</Popover>
|
||||
<Popover_Shadcn_ modal={false} open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type={(sorts || []).length > 0 ? 'link' : 'text'}
|
||||
icon={<List strokeWidth={1.5} className="text-foreground-light" />}
|
||||
>
|
||||
{btnText}
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-96" side="bottom" align="start">
|
||||
<SortOverlay table={table} sorts={sorts} onApplySorts={onApplySorts} />
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
export default SortPopover
|
||||
|
||||
export interface SortOverlayProps extends SortPopoverProps {}
|
||||
export interface SortOverlayProps {
|
||||
table: SupaTable
|
||||
sorts: string[]
|
||||
onApplySorts: (sorts: Sort[]) => void
|
||||
}
|
||||
|
||||
const SortOverlay = ({ table, sorts: sortsFromUrl, setParams }: SortOverlayProps) => {
|
||||
const SortOverlay = ({ table, sorts: sortsFromUrl, onApplySorts }: SortOverlayProps) => {
|
||||
const initialSorts = useMemo(
|
||||
() => formatSortURLParams((sortsFromUrl as string[]) ?? []),
|
||||
[sortsFromUrl]
|
||||
@@ -68,15 +85,6 @@ const SortOverlay = ({ table, sorts: sortsFromUrl, setParams }: SortOverlayProps
|
||||
setSorts([...sorts, { column: columnName as string, ascending: true }])
|
||||
}
|
||||
|
||||
function onApplySort() {
|
||||
setParams((prevParams) => {
|
||||
return {
|
||||
...prevParams,
|
||||
sort: sorts.map((sort) => `${sort.column}:${sort.ascending ? 'asc' : 'desc'}`),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onDeleteSort = useCallback((column: string) => {
|
||||
setSorts((currentSorts) => currentSorts.filter((sort) => sort.column !== column))
|
||||
}, [])
|
||||
@@ -125,7 +133,7 @@ const SortOverlay = ({ table, sorts: sortsFromUrl, setParams }: SortOverlayProps
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Popover.Separator />
|
||||
<PopoverSeparator_Shadcn_ />
|
||||
<div className="px-3 flex flex-row justify-between">
|
||||
{columns && columns.length > 0 ? (
|
||||
<DropdownControl
|
||||
@@ -135,19 +143,22 @@ const SortOverlay = ({ table, sorts: sortsFromUrl, setParams }: SortOverlayProps
|
||||
align="start"
|
||||
>
|
||||
<Button
|
||||
asChild
|
||||
type="text"
|
||||
iconRight={<IconChevronDown />}
|
||||
iconRight={<ChevronDown />}
|
||||
className="sb-grid-dropdown__item-trigger"
|
||||
>
|
||||
<span>{`Pick ${sorts.length > 1 ? 'another' : 'a'} column to sort by`}</span>
|
||||
Pick {sorts.length > 1 ? 'another' : 'a'} column to sort by
|
||||
</Button>
|
||||
</DropdownControl>
|
||||
) : (
|
||||
<p className="text-sm text-foreground-light">All columns have been added</p>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<Button disabled={isEqual(sorts, initialSorts)} type="default" onClick={onApplySort}>
|
||||
<Button
|
||||
disabled={isEqual(sorts, initialSorts)}
|
||||
type="default"
|
||||
onClick={() => onApplySorts(sorts)}
|
||||
>
|
||||
Apply sorting
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -562,30 +562,6 @@
|
||||
@apply block;
|
||||
}
|
||||
|
||||
/*
|
||||
header/filter/FilterDropdown
|
||||
*/
|
||||
|
||||
.sb-grid-filter-popover {
|
||||
@apply overflow-visible;
|
||||
}
|
||||
|
||||
.sb-grid-filter-popover__misc {
|
||||
@apply py-2;
|
||||
}
|
||||
|
||||
.sb-grid-filter-popover__misc__text {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
/*
|
||||
header/filter/FilterRow
|
||||
*/
|
||||
|
||||
.sb-grid-filter-row {
|
||||
@apply flex w-full items-center justify-between space-x-1;
|
||||
}
|
||||
|
||||
/*
|
||||
header/sort/SortDropdown
|
||||
*/
|
||||
|
||||
@@ -120,6 +120,7 @@ export {
|
||||
PopoverTrigger as PopoverTrigger_Shadcn_,
|
||||
PopoverContent as PopoverContent_Shadcn_,
|
||||
PopoverAnchor as PopoverAnchor_Shadcn_,
|
||||
PopoverSeparator as PopoverSeparator_Shadcn_,
|
||||
} from './src/components/shadcn/ui/popover'
|
||||
|
||||
export {
|
||||
|
||||
@@ -53,4 +53,11 @@ const PopoverContent = React.forwardRef<
|
||||
)
|
||||
PopoverContent.displayName = 'PopoverContent'
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
const PopoverSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, children, ...props }, ref) => (
|
||||
<div ref={ref} {...props} className={cn('w-full h-px bg-border-overlay', className)} />
|
||||
)
|
||||
)
|
||||
PopoverSeparator.displayName = 'PopoverSeparator'
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverSeparator }
|
||||
Reference in new issue
Block a user