Leave requests
6 waiting for your approval
- Sara MalikAnnual leave · 12–16 Oct5 days
- Luis OrtegaAnnual leave · 19–23 Oct5 days
- Chloe MartinSick leave · 8 Oct1 day
- Yuki TanakaParental leave · 2 Nov – 29 Jan63 days
- Ahmed RazaWork from abroad · 26–30 Oct5 days
- Mia JensenAnnual leave · 23 Dec – 2 Jan6 days
2 of 6 selected
bulk-actions.tsx
"use client";
import { useState } from "react";
import { LuCheck, LuMessageSquare, LuX } from "react-icons/lu";
import { Alert, Avatar, Badge, BulkActionBar, Button, Checkbox } from "@nexera-ui/react";
const REQUESTS = [
{
id: "r1",
name: "Sara Malik",
initials: "SM",
type: "Annual leave",
dates: "12–16 Oct",
days: 5,
},
{
id: "r2",
name: "Luis Ortega",
initials: "LO",
type: "Annual leave",
dates: "19–23 Oct",
days: 5,
},
{ id: "r3", name: "Chloe Martin", initials: "CM", type: "Sick leave", dates: "8 Oct", days: 1 },
{
id: "r4",
name: "Yuki Tanaka",
initials: "YT",
type: "Parental leave",
dates: "2 Nov – 29 Jan",
days: 63,
},
{
id: "r5",
name: "Ahmed Raza",
initials: "AR",
type: "Work from abroad",
dates: "26–30 Oct",
days: 5,
},
{
id: "r6",
name: "Mia Jensen",
initials: "MJ",
type: "Annual leave",
dates: "23 Dec – 2 Jan",
days: 6,
},
];
/** Bulk actions: pick several leave requests and approve, decline or message them at once from a floating bar. */
export default function BulkActions() {
const [pending, setPending] = useState(REQUESTS);
const [selected, setSelected] = useState<string[]>(["r1", "r2"]);
const [done, setDone] = useState<string | null>(null);
const toggle = (id: string, on: boolean) => {
setSelected((s) => (on ? [...s, id] : s.filter((x) => x !== id)));
};
const resolve = (verb: string) => {
setDone(
`${verb} ${selected.length} ${selected.length === 1 ? "request" : "requests"}. People were notified by email.`,
);
setPending((rows) => rows.filter((r) => !selected.includes(r.id)));
setSelected([]);
};
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="grid gap-1">
<h2 className="text-heading-h2 text-primary">Leave requests</h2>
<p className="text-body-default text-secondary">
{pending.length} waiting for your approval
</p>
</header>
{done ? <Alert tone="success" title={done} politeness="polite" /> : null}
<ul
aria-label="Leave requests waiting for approval"
className="grid divide-y divide-subtle rounded-lg border border-default bg-surface"
>
{pending.map((r) => (
<li key={r.id} className="flex items-start gap-3 p-4">
<Checkbox
className="mt-2.5"
aria-label={`Select ${r.name}, ${r.type}, ${r.dates}`}
isSelected={selected.includes(r.id)}
onChange={(on) => {
toggle(r.id, on);
}}
/>
<Avatar size="md" initials={r.initials} isDecorative />
<div className="grid min-w-0 flex-1 gap-0.5">
<span className="text-body-strong text-primary">{r.name}</span>
<span className="text-body-small text-secondary">
{r.type} · {r.dates}
</span>
</div>
<Badge
label={`${r.days} ${r.days === 1 ? "day" : "days"}`}
tone="neutral"
dot={false}
/>
</li>
))}
{pending.length === 0 ? (
<li className="p-6 text-center text-body-default text-secondary">
All caught up. New requests will show here.
</li>
) : null}
</ul>
{selected.length > 0 ? (
<div className="sticky bottom-4 flex justify-center">
<BulkActionBar
selection={`${selected.length} of ${pending.length} selected`}
isAllSelected={selected.length === pending.length}
onClearSelection={() => {
setSelected([]);
}}
{...(selected.length < pending.length
? {
onSelectAll: () => {
setSelected(pending.map((r) => r.id));
},
}
: {})}
selectAllLabel={`Select all ${pending.length}`}
>
<Button variant="secondary" leadingIcon={<LuMessageSquare />}>
Message
</Button>
<Button
variant="destructive"
leadingIcon={<LuX />}
onPress={() => {
resolve("Declined");
}}
>
Decline
</Button>
<Button
leadingIcon={<LuCheck />}
onPress={() => {
resolve("Approved");
}}
>
Approve
</Button>
</BulkActionBar>
</div>
) : null}
</div>
</div>
);
}
More lists, tables and records blocks
- Data tableSearch, sort, select rows and page through results.
- Filter barActive filters as removable chips above results.
- Filters drawerDetailed filters in a side panel.
- 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.