Filters drawer
Detailed filters in a side panel.
Built with
People
Showing 8 of 8
- Ayesha Khan Senior product designer · LondonFull-time
- Sara Malik Payroll specialist · LisbonFull-time
- Ahmed Raza Engineering manager · LahoreFull-time
- Mia Jensen Product manager · CopenhagenFull-time
- Luis Ortega Frontend engineer · MadridContractor
- Yuki Tanaka Recruiter · TokyoPart-time
- Chloe Martin Data analyst · ParisFull-time
- Omar Haddad Support lead · DubaiContractor
filters-drawer.tsx
"use client";
import { useState } from "react";
import { LuSlidersHorizontal } from "react-icons/lu";
import {
AvatarLabel,
Badge,
Button,
CheckboxField,
CheckboxGroup,
Drawer,
Select,
SelectOption,
} from "@nexera-ui/react";
// Sample data
interface Person {
id: string;
name: string;
initials: string;
role: string;
team: string;
email: string;
location: string;
status: "Active" | "On leave" | "Remote" | "Invited";
}
const PEOPLE: Person[] = [
{
id: "p1",
name: "Ayesha Khan",
initials: "AK",
role: "Senior product designer",
team: "Design",
email: "ayesha.khan@northwind.io",
location: "London",
status: "Active",
},
{
id: "p2",
name: "Sara Malik",
initials: "SM",
role: "Payroll specialist",
team: "People Ops",
email: "sara.malik@northwind.io",
location: "Lisbon",
status: "On leave",
},
{
id: "p3",
name: "Ahmed Raza",
initials: "AR",
role: "Engineering manager",
team: "Engineering",
email: "ahmed.raza@northwind.io",
location: "Lahore",
status: "Remote",
},
{
id: "p4",
name: "Mia Jensen",
initials: "MJ",
role: "Product manager",
team: "Product",
email: "mia.jensen@northwind.io",
location: "Copenhagen",
status: "Active",
},
{
id: "p5",
name: "Luis Ortega",
initials: "LO",
role: "Frontend engineer",
team: "Engineering",
email: "luis.ortega@northwind.io",
location: "Madrid",
status: "Active",
},
{
id: "p6",
name: "Yuki Tanaka",
initials: "YT",
role: "Recruiter",
team: "People Ops",
email: "yuki.tanaka@northwind.io",
location: "Tokyo",
status: "Active",
},
{
id: "p7",
name: "Chloe Martin",
initials: "CM",
role: "Data analyst",
team: "Finance",
email: "chloe.martin@northwind.io",
location: "Paris",
status: "Remote",
},
{
id: "p8",
name: "Omar Haddad",
initials: "OH",
role: "Support lead",
team: "Customer",
email: "omar.haddad@northwind.io",
location: "Dubai",
status: "Invited",
},
];
const TEAMS = [
"Design",
"Engineering",
"Finance",
"People Ops",
"Product",
"Customer",
] as const;
const TYPE: Record<string, string> = { p5: "Contractor", p6: "Part-time", p8: "Contractor" };
const typeOf = (id: string) => TYPE[id] ?? "Full-time";
const LOCATIONS = [...new Set(PEOPLE.map((p) => p.location))].sort();
interface Filters {
team: string;
location: string;
types: string[];
statuses: string[];
}
const NONE: Filters = { team: "any", location: "any", types: [], statuses: [] };
const matches = (f: Filters) =>
PEOPLE.filter(
(p) =>
(f.team === "any" || p.team === f.team) &&
(f.location === "any" || p.location === f.location) &&
(f.types.length === 0 || f.types.includes(typeOf(p.id))) &&
(f.statuses.length === 0 || f.statuses.includes(p.status)),
);
const countOf = (f: Filters) =>
Number(f.team !== "any") + Number(f.location !== "any") + f.types.length + f.statuses.length;
/** Filters drawer: detailed people filters in a side panel that opens from a Filters button and applies on demand. */
export default function FiltersDrawer() {
const [applied, setApplied] = useState<Filters>(NONE);
const [draft, setDraft] = useState<Filters>(NONE);
const [open, setOpen] = useState(false);
const results = matches(applied);
const preview = matches(draft).length;
const set = (patch: Partial<Filters>) => {
setDraft((d) => ({ ...d, ...patch }));
};
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid max-w-3xl gap-4">
<header className="flex flex-wrap items-end justify-between gap-3">
<div className="grid gap-1">
<h2 className="text-heading-h2 text-primary">People</h2>
<p className="text-body-default text-secondary" aria-live="polite">
Showing {results.length} of {PEOPLE.length}
</p>
</div>
<Drawer
isOpen={open}
onOpenChange={(next) => {
if (next) setDraft(applied);
setOpen(next);
}}
trigger={
<Button variant="secondary" leadingIcon={<LuSlidersHorizontal />}>
{countOf(applied) > 0 ? `Filters (${countOf(applied)})` : "Filters"}
</Button>
}
title="Filter people"
subtitle="Narrow the directory by team, place and contract."
secondaryAction={
<Button
variant="secondary"
size="md"
onPress={() => {
setDraft(NONE);
}}
>
Reset
</Button>
}
primaryAction={
<Button
size="md"
slot="close"
onPress={() => {
setApplied(draft);
}}
>
Show {preview} {preview === 1 ? "person" : "people"}
</Button>
}
>
<div className="grid gap-6">
<Select
label="Team"
value={draft.team}
onChange={(key) => {
set({ team: String(key ?? "any") });
}}
>
{["any", ...TEAMS].map((t) => (
<SelectOption key={t} id={t} label={t === "any" ? "Any team" : t} />
))}
</Select>
<Select
label="Location"
value={draft.location}
onChange={(key) => {
set({ location: String(key ?? "any") });
}}
>
{["any", ...LOCATIONS].map((l) => (
<SelectOption key={l} id={l} label={l === "any" ? "Any location" : l} />
))}
</Select>
<CheckboxGroup
label="Employment type"
value={draft.types}
onChange={(types) => {
set({ types });
}}
>
{["Full-time", "Part-time", "Contractor"].map((t) => (
<CheckboxField key={t} value={t} label={t} />
))}
</CheckboxGroup>
<CheckboxGroup
label="Status"
value={draft.statuses}
onChange={(statuses) => {
set({ statuses });
}}
>
{["Active", "Remote", "On leave", "Invited"].map((s) => (
<CheckboxField key={s} value={s} label={s} />
))}
</CheckboxGroup>
</div>
</Drawer>
</header>
<ul className="grid divide-y divide-subtle rounded-lg border border-default bg-surface">
{results.map((p) => (
<li key={p.id} className="flex flex-wrap items-center justify-between gap-2 p-4">
<AvatarLabel name={p.name} subtitle={`${p.role} · ${p.location}`} size="md" />
<Badge label={typeOf(p.id)} tone="neutral" dot={false} />
</li>
))}
{results.length === 0 ? (
<li className="p-6 text-center text-body-default text-secondary">
No one matches. Open Filters and reset them.
</li>
) : null}
</ul>
</div>
</div>
);
}
More lists, tables and records blocks
- Data tableSearch, sort, select rows and page through results.
- Bulk actionsA floating bar that acts on the selected rows.
- Filter barActive filters as removable chips above results.
- People directorySearchable list of people with presence.
- Record details drawerOpen a row to see details without leaving the list.
- Activity feedNewest first, with a 'new items' pill and paging.
- File browserFolders and files in a tree.
- Keyboard shortcutsA modal listing every shortcut.