Files
supabase/apps/studio/components/interfaces/HomePageActions.tsx
Danny WhiteandJoshen Lim 73ece8a470 chore(studio): improve projects presentation (#41179)
* improve admonition

* remove redundant loader

* copywriting

* table empty states

* improve integrations presentation

* match integration styling

* fix clipping and h-full container

* revert test

* improve status presentation

* nicer status

* card status improvements

* comment clarification

* prettier lint

* fix testes

* comment

* Fix infinite loading behaviour

* Clean u0p

* Replace NoFilterResults with NoSearchResults

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2025-12-12 20:35:43 +11:00

153 lines
5.1 KiB
TypeScript

import { keepPreviousData } from '@tanstack/react-query'
import { useDebounce } from '@uidotdev/usehooks'
import { Filter, Grid, List, Loader2, Plus, Search, X } from 'lucide-react'
import Link from 'next/link'
import { parseAsArrayOf, parseAsString, useQueryState } from 'nuqs'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { PROJECT_STATUS } from 'lib/constants'
import {
Button,
Checkbox_Shadcn_,
Label_Shadcn_,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
Popover_Shadcn_,
ToggleGroup,
ToggleGroupItem,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
interface HomePageActionsProps {
slug?: string
hideNewProject?: boolean
showViewToggle?: boolean
}
export const HomePageActions = ({
slug: _slug,
hideNewProject = false,
showViewToggle = false,
}: HomePageActionsProps) => {
const { slug: urlSlug } = useParams()
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
const slug = _slug ?? urlSlug
const [search, setSearch] = useQueryState('search', parseAsString.withDefault(''))
const debouncedSearch = useDebounce(search, 500)
const [filterStatus, setFilterStatus] = useQueryState(
'status',
parseAsArrayOf(parseAsString, ',').withDefault([])
)
const [viewMode, setViewMode] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.PROJECTS_VIEW, 'grid')
const { isFetching: isFetchingProjects } = useOrgProjectsInfiniteQuery(
{
slug,
search: search.length === 0 ? search : debouncedSearch,
statuses: filterStatus,
},
{ placeholderData: keepPreviousData }
)
return (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Input
placeholder="Search for a project"
icon={<Search />}
size="tiny"
className="w-32 md:w-64"
value={search}
onChange={(event) => setSearch(event.target.value)}
actions={[
search && (
<Button
size="tiny"
type="text"
icon={<X />}
onClick={() => setSearch('')}
className="p-0 h-5 w-5"
/>
),
]}
/>
<Popover_Shadcn_>
<PopoverTrigger_Shadcn_ asChild>
<Button
type={filterStatus.length === 0 ? 'dashed' : 'secondary'}
className="h-[26px] w-[26px]"
icon={<Filter />}
/>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_
className="p-0 w-56"
side="bottom"
align="center"
sideOffset={6}
portal={true}
>
<div className="px-3 pt-3 pb-2 flex flex-col gap-y-2">
<p className="text-xs">Filter projects by status</p>
<div className="flex flex-col">
{[
{ key: PROJECT_STATUS.ACTIVE_HEALTHY, label: 'Active' },
{ key: PROJECT_STATUS.INACTIVE, label: 'Paused' },
].map(({ key, label }) => (
<div className="flex items-center gap-x-2 py-1" key={key}>
<Checkbox_Shadcn_
id={key}
name={key}
checked={filterStatus.includes(key)}
onCheckedChange={() => {
if (filterStatus.includes(key)) {
setFilterStatus(filterStatus.filter((y) => y !== key))
} else {
setFilterStatus(filterStatus.concat([key]))
}
}}
/>
<Label_Shadcn_ htmlFor={key} className="capitalize text-xs w-full">
{label}
</Label_Shadcn_>
</div>
))}
</div>
</div>
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
{isFetchingProjects && <Loader2 className="animate-spin" size={14} />}
</div>
<div className="flex items-center gap-2">
{showViewToggle && viewMode && setViewMode && (
<ToggleGroup
type="single"
size="sm"
value={viewMode}
onValueChange={(value) => value && setViewMode(value as 'grid' | 'table')}
>
<ToggleGroupItem value="grid" size="sm" className="h-[26px] w-[26px] p-0">
<Grid size={14} strokeWidth={1.5} />
</ToggleGroupItem>
<ToggleGroupItem value="table" size="sm" className="h-[26px] w-[26px] p-0">
<List size={14} strokeWidth={1.5} />
</ToggleGroupItem>
</ToggleGroup>
)}
{projectCreationEnabled && !hideNewProject && (
<Button asChild icon={<Plus />} type="primary" size="tiny">
<Link href={`/new/${slug}`}>New project</Link>
</Button>
)}
</div>
</div>
)
}