- Accordion
- Agent Thinking
- Alert
- Alert Dialog
- Announcement
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Checkbox
- Checkbox Card
- Code Block
- Collapsible
- Color Picker
- Combobox
- Command
- Composer
- Composer Loader
- Context Menu
- Data Grid
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- File Upload
- Hover Card
- Input
- Input Group
- Input Otp
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Meter
- Native Select
- Navigation Menu
- Number Field
- Pagination
- Period Switcher
- Popover
- Progress
- Questionnaire
- Radio Card
- Radio Group
- Resizable
- Reveal
- Scroll Area
- Search Field
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Social Button
- Sonner
- Spinner
- Status Dot
- Switch
- Switch Card
- Table
- Tabs
- Tag Group
- Textarea
- Theme Toggle
- Time Field
- Toggle
- Toggle Group
- Tooltip
- Trace
- Tree View
- Agent Creator 01
- Agent Limits Card 01
- Agent Progress 01
- AI Chat 01
- AI Image Generation 01
- AI Profile 01
- Alerts Card 01
- Auth Card 01
- Calendar 01
- Composer 01
- Composer Attachments 01
- Composer Panel 01
- Finance Dashboard 01
- Home Dashboard 01
- Marketing Dashboard 01
- Medical Profile 01
- Meeting Scheduler 01
- Multi Agent Chat 01
- Notification Center 01
- Patient Info Card 01
- Project Board 01
- Questionnaire 01
- Settings Modal 01
- Sidebar 01
- Stat Cards 01
- Task List 01
- Web Search 01
- Activity Rings Card 01
- Area Chart Card 01
- Bar List Card 01
- Combo Chart Card 01
- Contributions Card 01
- Earnings Chart Card 01
- Funnel Chart Card 01
- Heatmap Chart Card 01
- Line Chart Card 01
- Most Active Days Card 01
- Orders Chart Card 01
- Radar Chart Card 01
- Radial Chart Card 01
- Revenue Chart Card 01
- Sankey Chart Card 01
- Scatter Chart Card 01
- Sleep Score Card 01
- Stage Bars Card 01
- Steps Card 01
AM Workspace Alex Project board
Project board
Backlog 5/5
To do 5/5
In progress 3/3
Review 2/2
Done 6/6
The board fills its parent, so give the parent a height. It uses local sample data. Sort and filter tickets, drag them between stages, or open a ticket to edit its details. With a ticket focused, Alt and the arrow keys move it between and within stages. The detail panel and the new ticket dialog are overlays: pass popupClassName with your theme scope so they match the page.
Source
"use client";
import { cn } from "cn";
import * as React from "react";
import { Board } from "./project-board-columns";
import type { Priority, Stage, Ticket } from "./project-board-data";
import { projects, seed } from "./project-board-data";
import { DetailPanel } from "./project-board-detail-panel";
import { NewTicketDialog } from "./project-board-new-ticket-dialog";
import { Toolbar } from "./project-board-toolbar";
import { useBoardDrag } from "./use-project-board-drag";
/**
* A five-stage ticket board with a toolbar, a detail panel and a new ticket
* dialog. It fills its parent, so give the parent a height. The panel and
* dialog are overlays: pass `popupClassName` (a theme scope) so they pick up
* the same tokens as the page. `leading` is placed before the title, for a
* navigation button. `header` swaps the whole breadcrumb and title row for
* your own, so an app shell can supply it.
*/
export function ProjectBoard({
className,
popupClassName,
leading,
header,
}: {
className?: string;
popupClassName?: string;
leading?: React.ReactNode;
/** Replaces the built-in breadcrumb and title row; receives the toolbar buttons. */
header?: (actions: React.ReactNode) => React.ReactNode;
}) {
const [tickets, setTickets] = React.useState(seed);
const [selected, setSelected] = React.useState<string | null>(null);
const [creating, setCreating] = React.useState(false);
const [readIds, setReadIds] = React.useState<string[]>([]);
const [sort, setSort] = React.useState("Manual order");
const [priority, setPriority] = React.useState("All priorities");
const [project, setProject] = React.useState("All projects");
const [showDone, setShowDone] = React.useState(true);
const [fill, setFill] = React.useState(true);
const [title, setTitle] = React.useState("");
const [description, setDescription] = React.useState("");
const [newStage, setNewStage] = React.useState<Stage | "">("");
const [newPriority, setNewPriority] = React.useState<Priority | "">("");
const [newProject, setNewProject] = React.useState<string>("");
const [newPerson, setNewPerson] = React.useState("");
const [keep, setKeep] = React.useState(false);
const [comment, setComment] = React.useState("");
const [editing, setEditing] = React.useState(false);
const current = tickets.find((t) => t.id === selected);
const update = (id: string, patch: Partial<Ticket>) =>
setTickets((all) => all.map((t) => (t.id === id ? { ...t, ...patch } : t)));
const move = (id: string, stage: Stage, before?: string) =>
setTickets((all) => {
const item = all.find((t) => t.id === id);
if (!item) {
return all;
}
const rest = all.filter((t) => t.id !== id);
const index = before ? rest.findIndex((t) => t.id === before) : -1;
const at =
index < 0 ? rest.findLastIndex((t) => t.stage === stage) + 1 : index;
rest.splice(at, 0, { ...item, stage });
return rest;
});
const {
start: startDrag,
moved,
drag,
dropped,
settling,
} = useBoardDrag(move);
const openCreate = (stage: Stage | "") => {
setNewStage(stage);
setCreating(true);
};
const create = (event: React.FormEvent) => {
event.preventDefault();
if (!title.trim()) {
return;
}
setTickets((all) => [
{
assignees: newPerson ? [newPerson] : [],
comments: [],
date: "just now",
description,
favorite: false,
group: "Planning",
id: `PB-${Math.max(...all.map((t) => Number(t.id.slice(3)))) + 1}`,
priority: newPriority || "Low",
project: newProject || projects[0],
stage: newStage || "Backlog",
title: title.trim(),
},
...all,
]);
setTitle("");
setDescription("");
if (!keep) {
setCreating(false);
}
};
return (
<div
data-board
className={cn(
"flex h-full min-h-0 flex-col gap-3 text-foreground",
className
)}
>
<Toolbar
{...{
fill,
header,
leading,
openCreate,
popupClassName,
priority,
project,
readIds,
setFill,
setPriority,
setProject,
setReadIds,
setShowDone,
setSort,
showDone,
sort,
}}
/>
<Board
{...{
drag,
dropped,
fill,
move,
moved,
openCreate,
priority,
project,
setSelected,
settling,
showDone,
sort,
startDrag,
tickets,
}}
/>
<DetailPanel
{...{
comment,
current,
editing,
move,
popupClassName,
setComment,
setEditing,
setSelected,
update,
}}
/>
<NewTicketDialog
{...{
create,
creating,
description,
keep,
newPerson,
newPriority,
newProject,
newStage,
popupClassName,
setCreating,
setDescription,
setKeep,
setNewPerson,
setNewPriority,
setNewProject,
setNewStage,
setTitle,
tickets,
title,
}}
/>
</div>
);
}The block is split into board, column, ticket-card, use-board-drag, toolbar, detail-panel, activity-chart, new-ticket-dialog, shared and data files in the same folder.
On this page