Skip to content

Commit 73f8f65

Browse files
committed
feat: update kanban
1 parent 9548181 commit 73f8f65

File tree

4 files changed

+133
-21
lines changed

4 files changed

+133
-21
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `Dice UI`
22

3-
Dice UI is a primitive ui component library for your design systems.
3+
Unstyled ui component library.
44

55
## Documentation
66

docs/app/(home)/pg/page.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from "@/components/ui/select";
1818
import { Textarea } from "@/components/ui/textarea";
1919
import { tricks } from "@/lib/data";
20+
import KanbanDemo from "@/registry/default/example/kanban-demo";
2021
import {
2122
Combobox,
2223
ComboboxAnchor,
@@ -33,6 +34,7 @@ export default function PlaygroundPage() {
3334
return (
3435
<Shell>
3536
<div className="h-screen bg-accent" />
37+
<KanbanDemo />
3638
<Textarea
3739
placeholder="Type here..."
3840
className="min-h-[80px] max-w-[40rem]"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"use client";
2+
3+
import * as Kanban from "@/registry/default/ui/kanban";
4+
import * as React from "react";
5+
6+
interface Task {
7+
id: string;
8+
title: string;
9+
priority: "low" | "medium" | "high";
10+
}
11+
12+
export default function KanbanDemo() {
13+
const [columns, setColumns] = React.useState<Record<string, Task[]>>({
14+
todo: [
15+
{ id: "1", title: "Add authentication", priority: "high" },
16+
{ id: "2", title: "Create API endpoints", priority: "medium" },
17+
{ id: "3", title: "Write documentation", priority: "low" },
18+
],
19+
inProgress: [
20+
{ id: "4", title: "Design system updates", priority: "high" },
21+
{ id: "5", title: "Implement dark mode", priority: "medium" },
22+
],
23+
done: [
24+
{ id: "6", title: "Setup project", priority: "high" },
25+
{ id: "7", title: "Initial commit", priority: "low" },
26+
],
27+
});
28+
29+
const columnData = [
30+
{ id: "todo", title: "Todo" },
31+
{ id: "inProgress", title: "In Progress" },
32+
{ id: "done", title: "Done" },
33+
];
34+
35+
return (
36+
<Kanban.Root
37+
columns={columns}
38+
onColumnsChange={setColumns}
39+
columnData={columnData}
40+
getColumnValue={(column) => column.id}
41+
getItemValue={(item) => item.id}
42+
>
43+
<Kanban.Board>
44+
{columnData.map((column) => (
45+
<Kanban.Column
46+
key={column.id}
47+
value={column.id}
48+
className="flex flex-col gap-4"
49+
>
50+
<div className="font-medium">{column.title}</div>
51+
{columns[column.id]?.map((task) => (
52+
<Kanban.Item key={task.id} value={task.id} asChild asGrip>
53+
<div className="rounded-md border bg-card p-4 shadow-sm">
54+
<div className="flex items-center justify-between">
55+
<div className="font-medium">{task.title}</div>
56+
<div
57+
className={`text-xs ${
58+
task.priority === "high"
59+
? "text-red-500"
60+
: task.priority === "medium"
61+
? "text-yellow-500"
62+
: "text-green-500"
63+
}`}
64+
>
65+
{task.priority}
66+
</div>
67+
</div>
68+
</div>
69+
</Kanban.Item>
70+
))}
71+
</Kanban.Column>
72+
))}
73+
</Kanban.Board>
74+
<Kanban.Overlay>
75+
{({ value }) => {
76+
const task = Object.values(columns)
77+
.flat()
78+
.find((task) => task.id === value);
79+
80+
if (!task) return null;
81+
82+
return (
83+
<div className="rounded-md border bg-card p-4 shadow-sm">
84+
<div className="flex items-center justify-between">
85+
<div className="font-medium">{task.title}</div>
86+
<div
87+
className={`text-xs ${
88+
task.priority === "high"
89+
? "text-red-500"
90+
: task.priority === "medium"
91+
? "text-yellow-500"
92+
: "text-green-500"
93+
}`}
94+
>
95+
{task.priority}
96+
</div>
97+
</div>
98+
</div>
99+
);
100+
}}
101+
</Kanban.Overlay>
102+
</Kanban.Root>
103+
);
104+
}

docs/registry/default/ui/kanban.tsx

+26-20
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,14 @@ interface KanbanProviderContextValue<T, C> {
5353
columnData: C[];
5454
activeId: UniqueIdentifier | null;
5555
setActiveId: (id: UniqueIdentifier | null) => void;
56-
getItemValue: (item: T) => UniqueIdentifier;
5756
getColumnValue: (column: C) => UniqueIdentifier;
57+
getItemValue: (item: T) => UniqueIdentifier;
5858
}
5959

60-
type KanbanRootContextValue = KanbanProviderContextValue<unknown, unknown>;
61-
62-
const KanbanRoot = React.createContext<KanbanRootContextValue | null>(null);
60+
const KanbanRoot = React.createContext<KanbanProviderContextValue<
61+
unknown,
62+
unknown
63+
> | null>(null);
6364
KanbanRoot.displayName = ROOT_NAME;
6465

6566
function useKanbanRoot() {
@@ -72,8 +73,8 @@ function useKanbanRoot() {
7273

7374
type KanbanProps<T, C> = DndContextProps & {
7475
columns: Record<UniqueIdentifier, T[]>;
76+
onColumnsChange?: (columns: Record<UniqueIdentifier, T[]>) => void;
7577
columnData: C[];
76-
onValueChange?: (columns: Record<UniqueIdentifier, T[]>) => void;
7778
onMove?: (event: DragEndEvent) => void;
7879
sensors?: DndContextProps["sensors"];
7980
} & (T extends object
@@ -87,7 +88,7 @@ function Kanban<T, C>(props: KanbanProps<T, C>) {
8788
const {
8889
columns,
8990
columnData,
90-
onValueChange,
91+
onColumnsChange,
9192
sensors: sensorsProp,
9293
onMove,
9394
getItemValue: getItemValueProp,
@@ -132,7 +133,7 @@ function Kanban<T, C>(props: KanbanProps<T, C>) {
132133
[getColumnValueProp],
133134
);
134135

135-
const contextValue = React.useMemo(
136+
const contextValue = React.useMemo<KanbanProviderContextValue<T, C>>(
136137
() => ({
137138
id,
138139
columns,
@@ -145,20 +146,25 @@ function Kanban<T, C>(props: KanbanProps<T, C>) {
145146
[id, columns, columnData, activeId, getItemValue, getColumnValue],
146147
);
147148

148-
const findContainer = (id: UniqueIdentifier) => {
149-
if (id in columns) return id;
149+
const getContainer = React.useCallback(
150+
(id: UniqueIdentifier) => {
151+
if (id in columns) return id;
150152

151-
for (const [columnId, items] of Object.entries(columns)) {
152-
if (items.some((item) => getItemValue(item) === id)) {
153-
return columnId;
153+
for (const [columnId, items] of Object.entries(columns)) {
154+
if (items.some((item) => getItemValue(item) === id)) {
155+
return columnId;
156+
}
154157
}
155-
}
156158

157-
return null;
158-
};
159+
return null;
160+
},
161+
[columns, getItemValue],
162+
);
159163

160164
return (
161-
<KanbanRoot.Provider value={contextValue as KanbanRootContextValue}>
165+
<KanbanRoot.Provider
166+
value={contextValue as KanbanProviderContextValue<unknown, unknown>}
167+
>
162168
<DndContext
163169
id={id}
164170
sensors={sensorsProp ?? sensors}
@@ -174,8 +180,8 @@ function Kanban<T, C>(props: KanbanProps<T, C>) {
174180
return;
175181
}
176182

177-
const activeContainer = findContainer(active.id);
178-
const overContainer = findContainer(over.id);
183+
const activeContainer = getContainer(active.id);
184+
const overContainer = getContainer(over.id);
179185

180186
if (!activeContainer || !overContainer) {
181187
setActiveId(null);
@@ -217,7 +223,7 @@ function Kanban<T, C>(props: KanbanProps<T, C>) {
217223
}
218224

219225
overColumn.splice(overIndex, 0, movedItem);
220-
onValueChange?.(newColumns);
226+
onColumnsChange?.(newColumns);
221227
}
222228
} else {
223229
const items = columns[activeContainer];
@@ -248,7 +254,7 @@ function Kanban<T, C>(props: KanbanProps<T, C>) {
248254
activeIndex,
249255
overIndex,
250256
);
251-
onValueChange?.(newColumns);
257+
onColumnsChange?.(newColumns);
252258
}
253259
}
254260
}

0 commit comments

Comments
 (0)