App screens
File manager
A file-manager home screen: six quick-action tiles (Create, Upload, Create Folder, Edit PDF, Get Signatures, Sign Yourself), a Suggested Folders grid, and a Suggested Files section that toggles between a card grid and a compact list — each file or folder carrying its own Dropdown menu.
file-manager.tsx
How it's built
1
The action tiles are real Buttons with their content reflowed to a vertical layout via className — variant/color still come from the real component, not a hand-rolled one.
2
Grid vs. list is one `view` state driving two different layouts over the same `FILES` array — no duplicated data.
3
Each file/folder card's `⋮` is a Dropdown with an accessibly-named icon trigger, exactly like every other per-row menu on this site.
Source
One file · copy & paste"use client";
import { useState } from "react";
import { Button } from "oks-ui/button";
import { Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from "oks-ui/dropdown";
import "oks-ui/button.css";
import "oks-ui/dropdown.css";
import { GridIcon, MoreIcon, PlusIcon } from "../../showcase/icons";
// An upward arrow into a tray — "upload".
function UploadIcon({ size = 20 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M10 12.5V3.5M6.5 7l3.5-3.5L13.5 7" />
<path d="M3.5 13v2a1.5 1.5 0 0 0 1.5 1.5h10a1.5 1.5 0 0 0 1.5-1.5v-2" />
</svg>
);
}
// A folder with a plus badge — "create folder".
function FolderPlusIcon({ size = 20 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M2.5 5.5a1 1 0 0 1 1-1h3.8l1.5 1.8h7.2a1 1 0 0 1 1 1v7.2a1 1 0 0 1-1 1h-12.5a1 1 0 0 1-1-1Z" />
<path d="M10 9v4M8 11h4" />
</svg>
);
}
// A pencil over a rectangle — "edit a document".
function EditDocIcon({ size = 20 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M12 3.5 16.5 8 8 16.5H3.5V12Z" />
</svg>
);
}
// A flowing signature line — "get signatures".
function SignatureIcon({ size = 20 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M2.5 14c1.5-1 2.5-4 3.5-4s.5 3 2 3 2-5 3.5-5 1 4 2.5 4 1.5-2.5 3.5-2.5" />
<path d="M3 17h14" />
</svg>
);
}
// A pen writing on a line — "sign yourself".
function SignPenIcon({ size = 20 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M11 3.5 16.5 9 7 18.5H2.5V14Z" />
<path d="M2.5 18.5h4" />
</svg>
);
}
// A folder, filled — used for folder cards.
function FolderIcon({ size = 20, className }: { size?: number; className?: string }) {
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="currentColor" className={className} aria-hidden="true">
<path d="M2 5a1 1 0 0 1 1-1h4.5l1.8 2h7.7a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1Z" />
</svg>
);
}
// Three horizontal lines — the "list view" half of a grid/list toggle.
function ListViewIcon({ size = 16 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" aria-hidden="true">
<path d="M3 4h10M3 8h10M3 12h10" />
</svg>
);
}
// A circled "i" — "details".
function InfoIcon({ size = 16 }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="8" cy="8" r="6.5" />
<path d="M8 7.2v4M8 5.2v.1" />
</svg>
);
}
const TOOLS = [
{ key: "create", label: "Create", icon: <PlusIcon size={20} />, primary: true },
{ key: "upload", label: "Upload or Drop", icon: <UploadIcon /> },
{ key: "folder", label: "Create Folder", icon: <FolderPlusIcon /> },
{ key: "edit", label: "Edit PDF", icon: <EditDocIcon /> },
{ key: "sig", label: "Get Signatures", icon: <SignatureIcon /> },
{ key: "sign", label: "Sign Yourself", icon: <SignPenIcon /> },
];
const OWNER = "Alex Rivera";
const FOLDERS = ["Tech Innovations", "Digital Masterpieces", "Software Solutions", "Code Inspirations"];
type DocVariant = "invoice" | "resume" | "letter" | "photo";
type FileItem = { id: number; name: string; variant: DocVariant };
const FILES: FileItem[] = [
{ id: 1, name: "2024 Product Overview", variant: "invoice" },
{ id: 2, name: "Annual Review", variant: "resume" },
{ id: 3, name: "Trends Development", variant: "letter" },
{ id: 4, name: "Performance Analysis", variant: "letter" },
{ id: 5, name: "Case Study", variant: "resume" },
{ id: 6, name: "Effectiveness Report", variant: "letter" },
{ id: 7, name: "Landscape Overview", variant: "letter" },
{ id: 8, name: "Brunch Recipe", variant: "photo" },
];
function DocThumb({ variant }: { variant: DocVariant }) {
if (variant === "photo") {
return (
<div
className="h-full w-full"
style={{ background: "linear-gradient(140deg, #e0623a 0%, #7a2d2d 55%, #1c1420 100%)" }}
/>
);
}
if (variant === "invoice") {
return (
<div className="flex h-full w-full flex-col gap-1.5 p-3">
<div className="flex items-center justify-between">
<span className="h-1.5 w-8 rounded-full bg-ink-faint/60" />
<span className="h-1.5 w-10 rounded-full bg-[color:var(--oks-color-success-400)]" />
</div>
<span className="mt-1 h-1 w-14 rounded-full bg-ink-faint/40" />
<span className="h-1 w-16 rounded-full bg-ink-faint/40" />
<div className="mt-2 flex flex-col gap-1 border-t border-line pt-2">
{Array.from({ length: 3 }, (_, i) => (
<div key={i} className="flex justify-between">
<span className="h-1 w-10 rounded-full bg-ink-faint/40" />
<span className="h-1 w-6 rounded-full bg-ink-faint/40" />
</div>
))}
</div>
</div>
);
}
if (variant === "resume") {
return (
<div className="flex h-full w-full flex-col gap-2 p-3">
<span className="h-2 w-16 rounded-full bg-ink-faint/60" />
<span className="h-1 w-10 rounded-full bg-[color:var(--oks-color-danger-400)]" />
<div className="mt-2 flex flex-col gap-1">
<span className="h-1 w-8 rounded-full bg-ink-faint/40" />
<span className="h-1 w-full rounded-full bg-ink-faint/30" />
<span className="h-1 w-full rounded-full bg-ink-faint/30" />
</div>
<div className="mt-2 flex flex-col gap-1">
<span className="h-1 w-8 rounded-full bg-ink-faint/40" />
<span className="h-1 w-full rounded-full bg-ink-faint/30" />
</div>
</div>
);
}
return (
<div className="flex h-full w-full flex-col gap-1.5 p-3">
<span className="h-1 w-16 self-end rounded-full bg-[color:var(--oks-color-info-400)]" />
<span className="mt-2 h-1 w-full rounded-full bg-ink-faint/30" />
<span className="h-1 w-full rounded-full bg-ink-faint/30" />
<span className="h-1 w-3/4 rounded-full bg-ink-faint/30" />
<span className="mt-2 h-1 w-full rounded-full bg-ink-faint/30" />
<span className="h-1 w-full rounded-full bg-ink-faint/30" />
</div>
);
}
function CardMenu({ label }: { label: string }) {
return (
<Dropdown placement="bottom-end">
<DropdownTrigger>
<Button isIconOnly aria-label={label} variant="ghost" size="xs">
<MoreIcon size={16} />
</Button>
</DropdownTrigger>
<DropdownMenu aria-label={label}>
<DropdownItem key="rename">Rename</DropdownItem>
<DropdownItem key="share">Share</DropdownItem>
<DropdownItem key="delete" classNames={{ title: "text-[color:var(--oks-color-danger-700)]" }}>
Delete
</DropdownItem>
</DropdownMenu>
</Dropdown>
);
}
export function FileManager() {
const [view, setView] = useState<"grid" | "list">("grid");
return (
// @container has to live on its OWN wrapper, not an element that also
// uses @xl: utilities conditioned by it — an element can't query its
// own container for its own styling (circular per the CSS Containment
// spec), so those utilities would silently be evaluated against the
// wrong (ancestor) container and never apply.
<div className="@container w-full">
<div className="flex w-full flex-col gap-6 bg-paper p-6">
<div className="grid grid-cols-3 gap-3 @xl:grid-cols-6">
{TOOLS.map((t) => (
<Button
key={t.key}
variant={t.primary ? "solid" : "bordered"}
className={`h-auto flex-col items-start gap-3 rounded-[var(--r-lg)] p-4 text-left ${
t.primary ? "bg-ink text-paper hover:opacity-90" : ""
}`}
>
{t.icon}
<span className="text-[0.85rem] font-medium">{t.label}</span>
</Button>
))}
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
{/* h2, not h3: this preview's own outline starts fresh at the
pattern page's h1 with nothing in between, so h3 here would
skip a level (axe's heading-order). */}
<h2 className="text-[1.05rem] font-bold text-ink">Suggested Folders</h2>
<Button variant="bordered" size="sm" radius="full">
View more
</Button>
</div>
<div className="grid grid-cols-2 gap-4 @xl:grid-cols-4">
{FOLDERS.map((name) => (
<div key={name} className="flex flex-col gap-2">
<div className="flex aspect-[4/3] items-center justify-center rounded-[var(--r-lg)] bg-paper-sunken">
<FolderIcon size={56} className="text-[color:var(--oks-color-info-400)]" />
</div>
<div className="flex items-center gap-2">
<FolderIcon size={16} className="shrink-0 text-[color:var(--oks-color-info-500)]" />
<div className="min-w-0 flex-1">
<p className="truncate text-[0.85rem] font-medium text-ink">{name}</p>
<p className="truncate text-[0.75rem] text-ink-faint">Folder · {OWNER}</p>
</div>
<CardMenu label={`${name} options`} />
</div>
</div>
))}
</div>
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<h2 className="text-[1.05rem] font-bold text-ink">Suggested Files</h2>
<div className="flex items-center gap-1">
<Button isIconOnly aria-label="List view" variant={view === "list" ? "soft" : "ghost"} color={view === "list" ? "primary" : "default"} size="sm" onClick={() => setView("list")}>
<ListViewIcon />
</Button>
<Button isIconOnly aria-label="Grid view" variant={view === "grid" ? "soft" : "ghost"} color={view === "grid" ? "primary" : "default"} size="sm" onClick={() => setView("grid")}>
<GridIcon size={16} />
</Button>
<Button isIconOnly aria-label="Show details" variant="ghost" size="sm">
<InfoIcon />
</Button>
</div>
</div>
{view === "grid" ? (
<div className="grid grid-cols-2 gap-4 @xl:grid-cols-4">
{FILES.map((f) => (
<div key={f.id} className="flex flex-col gap-2">
<div className="relative aspect-[4/3] overflow-hidden rounded-[var(--r-lg)] border border-line bg-paper-sunken">
<DocThumb variant={f.variant} />
<span className="absolute bottom-2 left-2 rounded-[var(--r-xs,0.25rem)] bg-[color:var(--oks-color-danger-500)] px-1.5 py-0.5 text-[0.65rem] font-bold text-white">
PDF
</span>
</div>
<div className="flex items-center gap-2">
<div className="min-w-0 flex-1">
<p className="truncate text-[0.85rem] font-medium text-ink">{f.name}</p>
<p className="truncate text-[0.75rem] text-ink-faint">PDF · {OWNER}</p>
</div>
<CardMenu label={`${f.name} options`} />
</div>
</div>
))}
</div>
) : (
<div className="flex flex-col divide-y divide-line">
{FILES.map((f) => (
<div key={f.id} className="flex items-center gap-3 py-2.5">
<span className="flex h-8 w-6 shrink-0 items-center justify-center rounded-[var(--r-xs,0.25rem)] bg-[color:var(--oks-color-danger-500)] text-[0.55rem] font-bold text-white">
PDF
</span>
<div className="min-w-0 flex-1">
<p className="truncate text-[0.85rem] font-medium text-ink">{f.name}</p>
<p className="truncate text-[0.75rem] text-ink-faint">PDF · {OWNER}</p>
</div>
<CardMenu label={`${f.name} options`} />
</div>
))}
</div>
)}
</div>
</div>
</div>
);
}More patterns
App screens
Analytics dashboardA full fintech app screen — collapsible sidebar, header search, wallet cards, an earnings chart, savings goals, and a transactions table.
App screens
Settings panelA real settings screen — top tabs, two-column sections, and a sortable, selectable billing history table.
App screens
Data table viewA full documents list screen — status Tabs, a filter toolbar, and a sortable, selectable Table with real recipient avatars.