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:
Joshen Lim authored and GitHub committed 2024-05-13 12:36:51 +08:00
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>
-24
View File
@@ -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
*/
+1
View File
@@ -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 }