File browser
Folders and files in a tree.
Built with
Documents
Shared with People Ops · 8 files
Contracts
Right-click a file, or focus it and press Shift+F10, for actions.
- Offer letter – Daniel Okafor.pdfSaved182 KB · updated 6 Oct by Yuki Tanaka
- Contract – Tom Becker.pdfSaved236 KB · updated 30 Jun by Yuki Tanaka
file-browser.tsx
"use client";
import { useState } from "react";
import {
LuDownload,
LuFileSpreadsheet,
LuFileText,
LuFolder,
LuPencil,
LuShare2,
LuTrash2,
} from "react-icons/lu";
import { ContextMenu, FileItem, MenuDivider, MenuItem, Tree, TreeItem } from "@nexera-ui/react";
interface Doc {
name: string;
meta: string;
}
const pdf = (name: string, meta: string): Doc => ({ name: `${name}.pdf`, meta });
const FILES: Record<string, Doc[]> = {
policies: [
pdf("Employee handbook 2026", "2.4 MB · updated 14 Jan by Elena Rossi"),
pdf("Remote work policy", "310 KB · updated 2 Jun by Elena Rossi"),
pdf("Code of conduct", "540 KB · updated 9 Mar by Priya Nair"),
],
contracts: [
pdf("Offer letter – Daniel Okafor", "182 KB · updated 6 Oct by Yuki Tanaka"),
pdf("Contract – Tom Becker", "236 KB · updated 30 Jun by Yuki Tanaka"),
],
payroll: [
{ name: "September 2026 payroll.xlsx", meta: "96 KB · updated 30 Sep by Sara Malik" },
{ name: "Overtime rates.xlsx", meta: "41 KB · updated 22 Sep by Sara Malik" },
],
onboarding: [pdf("First week plan", "128 KB · updated 1 Oct by Ayesha Khan")],
};
const FOLDER_NAMES: Record<string, string> = {
policies: "Company policies",
contracts: "Contracts",
payroll: "Payroll",
onboarding: "Onboarding",
};
const folder = <LuFolder />;
/** File browser: shared HR folders in a tree, the files of the chosen folder, and a context menu on every file. */
export default function FileBrowser() {
const [folderId, setFolderId] = useState("contracts");
const [notice, setNotice] = useState("");
const files = FILES[folderId] ?? [];
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid max-w-5xl gap-4">
<header className="grid gap-1">
<h2 className="text-heading-h2 text-primary">Documents</h2>
<p className="text-body-default text-secondary">Shared with People Ops · 8 files</p>
</header>
<div className="grid gap-4 @3xl:grid-cols-[16rem_1fr]">
<nav
aria-labelledby="folders-title"
className="grid content-start gap-2 rounded-lg border border-default bg-surface p-3"
>
<h3 id="folders-title" className="px-2 text-component-label text-muted">
Folders
</h3>
<Tree
aria-labelledby="folders-title"
selectionMode="single"
disallowEmptySelection
selectedKeys={[folderId]}
onSelectionChange={(keys) => {
const next = keys === "all" ? undefined : [...keys][0];
if (next !== undefined) setFolderId(String(next));
}}
defaultExpandedKeys={["people-ops"]}
>
<TreeItem id="policies" label="Company policies" leafIcon={folder} count="3" />
<TreeItem
id="people-ops"
label="People Ops"
branchIcon={folder}
textValue="People Ops"
>
<TreeItem id="contracts" label="Contracts" leafIcon={folder} count="2" />
<TreeItem id="payroll" label="Payroll" leafIcon={folder} count="2" />
</TreeItem>
<TreeItem id="onboarding" label="Onboarding" leafIcon={folder} count="1" />
</Tree>
</nav>
<section
aria-labelledby="files-title"
className="grid content-start gap-3 rounded-lg border border-default bg-surface p-4"
>
<div className="grid gap-1">
<h3 id="files-title" className="text-heading-h4 text-primary">
{FOLDER_NAMES[folderId] ?? "Files"}
</h3>
<p id="files-hint" className="text-body-small text-muted">
Right-click a file, or focus it and press Shift+F10, for actions.
</p>
</div>
<ul className="grid gap-2">
{files.map((file) => (
<li key={file.name}>
<ContextMenu
aria-label={`Actions for ${file.name}`}
onAction={(key) => {
setNotice(`${String(key)} “${file.name}”`);
}}
trigger={
<div
role="button"
tabIndex={0}
aria-describedby="files-hint"
className="rounded-md outline-hidden focus-visible:focus-ring"
>
<FileItem
name={file.name}
meta={file.meta}
status="complete"
statusLabels={{ complete: "Saved" }}
icon={
file.name.endsWith(".xlsx") ? <LuFileSpreadsheet /> : <LuFileText />
}
/>
</div>
}
>
<MenuItem id="Downloaded" label="Download" icon={<LuDownload />} />
<MenuItem id="Shared" label="Share" icon={<LuShare2 />} />
<MenuItem id="Renamed" label="Rename" icon={<LuPencil />} />
<MenuDivider />
<MenuItem
id="Moved to bin"
label="Move to bin"
type="destructive"
icon={<LuTrash2 />}
/>
</ContextMenu>
</li>
))}
</ul>
<p className="text-body-small text-secondary" aria-live="polite">
{notice ? `${notice}. This is a demo, so nothing changed.` : ""}
</p>
</section>
</div>
</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.
- 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.
- Keyboard shortcutsA modal listing every shortcut.