UNPKG

react-kanban-kit

Version:

A flexible and customizable Kanban board component for React applications, built with TypeScript and modern drag-and-drop functionality powered by Atlassian's pragmatic-drag-and-drop.

534 lines (453 loc) 14.9 kB
# React Kanban Kit A flexible and customizable Kanban board component for React applications, built with TypeScript and modern drag-and-drop functionality powered by Atlassian's pragmatic-drag-and-drop. ## Demo Check out the live demo: [https://react-kanban-kit.netlify.app/](https://react-kanban-kit.netlify.app/) ## Features - 🎯 **Drag and Drop**: Cards and columns with smooth animations - 📱 **Responsive Design**: Works on desktop, tablet, and mobile - 🎨 **Highly Customizable**: Custom renderers for cards, headers, footers, and more - 🔄 **Virtual Scrolling**: Optimized performance for large datasets - 📦 **TypeScript Support**: Full type safety and IntelliSense - 🎮 **View-Only Mode**: Disable interactions when needed - 🎯 **Skeleton Loading**: Built-in loading states with animations - 🎨 **Custom Styling**: Function-based styling with access to data context - 🔥 **Modern Architecture**: Built with React hooks and clean separation of concerns ## Installation ```bash npm install react-kanban-kit # or yarn add react-kanban-kit # or pnpm add react-kanban-kit ``` ## Basic Usage ```tsx import { Kanban } from "react-kanban-kit"; const MyKanbanBoard = () => { const dataSource = { root: { id: "root", title: "Root", children: ["col-1", "col-2", "col-3"], totalChildrenCount: 3, parentId: null, }, "col-1": { id: "col-1", title: "To Do", children: ["task-1", "task-2"], totalChildrenCount: 2, parentId: "root", }, "col-2": { id: "col-2", title: "In Progress", children: ["task-3"], totalChildrenCount: 1, parentId: "root", }, "col-3": { id: "col-3", title: "Done", children: ["task-4"], totalChildrenCount: 1, parentId: "root", }, "task-1": { id: "task-1", title: "Design Homepage", parentId: "col-1", children: [], totalChildrenCount: 0, type: "card", content: { description: "Create wireframes and mockups for the homepage", priority: "high", }, }, "task-2": { id: "task-2", title: "Setup Database", parentId: "col-1", children: [], totalChildrenCount: 0, type: "card", }, // ... more tasks }; const configMap = { card: { render: ({ data, column, index, isDraggable }) => ( <div className="kanban-card"> <h3>{data.title}</h3> {data.content?.description && <p>{data.content.description}</p>} <div className="card-meta"> {data.content?.priority && ( <span className={`priority ${data.content.priority}`}> {data.content.priority} </span> )} </div> </div> ), isDraggable: true, }, }; return ( <Kanban dataSource={dataSource} configMap={configMap} onCardMove={(move) => { console.log("Card moved:", move); // Handle card movement }} onColumnMove={(move) => { console.log("Column moved:", move); // Handle column reordering }} /> ); }; ``` ## Advanced Usage ### Custom Card Types and Renderers ```tsx const configMap = { card: { render: ({ data, column, index, isDraggable }) => ( <div className="task-card"> <h4>{data.title}</h4> <p>{data.content?.description}</p> <div className="card-footer"> <span className="assignee">{data.content?.assignee}</span> <span className="due-date">{data.content?.dueDate}</span> </div> </div> ), isDraggable: true, }, divider: { render: ({ data }) => ( <div className="divider"> <hr /> <span>{data.title}</span> </div> ), isDraggable: false, }, footer: { render: ({ data, column }) => ( <button className="add-card-btn">+ Add card to {column.title}</button> ), isDraggable: false, }, }; ``` ### Custom Column Headers and Footers ```tsx <Kanban dataSource={dataSource} configMap={configMap} renderColumnHeader={(column) => ( <div className="custom-header"> <h3>{column.title}</h3> <span className="count">{column.totalChildrenCount}</span> <button className="column-menu">⋯</button> </div> )} renderColumnFooter={(column) => ( <div className="column-footer"> <button>Add New Card</button> </div> )} // Column adder allowColumnAdder={true} renderColumnAdder={() => ( <button className="add-column-btn">+ Add Column</button> )} // List footer (shown at bottom of each column) allowListFooter={(column) => column.id !== "done"} renderListFooter={(column) => ( <div className="list-footer"> <button>+ Add another card</button> </div> )} /> ``` ### Drag and Drop Customization ```tsx <Kanban // Custom drag previews renderCardDragPreview={(card, info) => ( <div className="drag-preview"> <h4>{card.title}</h4> <span>Moving to...</span> </div> )} renderCardDragIndicator={(card, info) => ( <div className="drop-indicator" style={{ height: info.height }} /> )} // DND state change callbacks onCardDndStateChange={(info) => { console.log("Card DND state:", info.state.type); if (info.state.type === "is-dragging") { // Card is being dragged } }} onColumnDndStateChange={(info) => { console.log("Column DND state:", info.state.type); if (info.state.type === "is-card-over") { // Card is being dragged over this column } }} /> ``` ### Advanced Styling and Customization ```tsx <Kanban // Root container styling rootClassName="my-kanban-board" rootStyle={{ backgroundColor: "#f5f5f5", padding: "20px" }} // Column styling (functions get access to column data) columnWrapperStyle={(column) => ({ backgroundColor: column.id === "urgent" ? "#ffe6e6" : "#ffffff", border: `2px solid ${column.content?.color || "#ddd"}`, })} columnWrapperClassName={(column) => `column-wrapper ${column.content?.theme || "default"}` } columnHeaderStyle={(column) => ({ backgroundColor: column.content?.headerColor || "#f8f9fa", color: column.content?.textColor || "#333", })} columnStyle={(column) => ({ minHeight: column.totalChildrenCount > 10 ? "800px" : "400px", })} columnClassName={(column) => column.totalChildrenCount === 0 ? "empty-column" : "has-items" } // Card styling cardWrapperStyle={(card, column) => ({ marginBottom: "8px", opacity: card.content?.archived ? 0.5 : 1, })} cardWrapperClassName="custom-card-wrapper" cardsGap={12} // Gap between cards in pixels // Column list content styling columnListContentStyle={(column) => ({ padding: column.totalChildrenCount === 0 ? "40px 16px" : "8px", })} columnListContentClassName={(column) => `column-content ${column.totalChildrenCount === 0 ? "empty" : "filled"}` } /> ``` ### Loading States and Virtualization ```tsx <Kanban dataSource={dataSource} configMap={configMap} // Custom skeleton loading renderSkeletonCard={({ index, column }) => ( <div className="skeleton-card"> <div className="skeleton-title"></div> <div className="skeleton-content"></div> <div className="skeleton-footer"></div> </div> )} // Virtual scrolling (default: true) virtualization={true} // Load more functionality loadMore={(columnId) => { console.log(`Loading more items for column: ${columnId}`); // Fetch and add more items }} // Scroll event handling onScroll={(event, column) => { const { scrollTop, scrollHeight, clientHeight } = event.target; const isNearBottom = scrollTop + clientHeight >= scrollHeight - 100; if (isNearBottom) { // Load more items when near bottom loadMore?.(column.id); } }} /> ``` ### View-Only Mode ```tsx <Kanban dataSource={dataSource} configMap={configMap} viewOnly={true} // Disables all drag and drop interactions /> ``` ## Props Reference ### Core Props | Prop | Type | Description | | ------------ | ----------- | ---------------------------------------------------- | | `dataSource` | `BoardData` | **Required.** The data structure for the board | | `configMap` | `ConfigMap` | **Required.** Configuration for different card types | | `viewOnly` | `boolean` | Disable all drag and drop interactions | ### Data Loading | Prop | Type | Description | | -------------------- | ---------------------------------- | ---------------------------- | | `loadMore` | `(columnId: string) => void` | Load more items for a column | | `renderSkeletonCard` | `({ index, column }) => ReactNode` | Custom skeleton loader | ### Drag and Drop Events | Prop | Type | Description | | ------------------------ | ---------------------------- | -------------------------------- | | `onCardMove` | `(move: CardMove) => void` | Fired when a card is moved | | `onColumnMove` | `(move: ColumnMove) => void` | Fired when a column is reordered | | `onCardDndStateChange` | `(info: DndState) => void` | Card drag state changes | | `onColumnDndStateChange` | `(info: DndState) => void` | Column drag state changes | ### Drag and Drop Customization | Prop | Type | Description | | ------------------------- | --------------------------- | ------------------------ | | `renderCardDragPreview` | `(card, info) => ReactNode` | Custom card drag preview | | `renderCardDragIndicator` | `(card, info) => ReactNode` | Custom drop indicator | ### Column Customization | Prop | Type | Description | | --------------------- | ---------------------------------- | ----------------------------- | | `renderColumnHeader` | `(column: BoardItem) => ReactNode` | Custom column header | | `renderColumnFooter` | `(column: BoardItem) => ReactNode` | Custom column footer | | `renderColumnWrapper` | `(column, props) => ReactNode` | Wrap entire column | | `allowColumnAdder` | `boolean` | Show add column button | | `renderColumnAdder` | `() => ReactNode` | Custom add column button | | `renderListFooter` | `(column: BoardItem) => ReactNode` | Footer at bottom of card list | | `allowListFooter` | `(column: BoardItem) => boolean` | Show list footer per column | ### Styling Props (Functions) | Prop | Type | Description | | ------------------------ | -------------------------------------- | -------------------------- | | `columnWrapperStyle` | `(column: BoardItem) => CSSProperties` | Column wrapper styles | | `columnHeaderStyle` | `(column: BoardItem) => CSSProperties` | Column header styles | | `columnStyle` | `(column: BoardItem) => CSSProperties` | Column inner styles | | `columnListContentStyle` | `(column: BoardItem) => CSSProperties` | Column content area styles | | `cardWrapperStyle` | `(card, column) => CSSProperties` | Card wrapper styles | ### Styling Props (Class Names) | Prop | Type | Description | | ---------------------------- | ------------------------------- | -------------------- | | `rootClassName` | `string` | Root container class | | `columnWrapperClassName` | `(column: BoardItem) => string` | Column wrapper class | | `columnHeaderClassName` | `(column: BoardItem) => string` | Column header class | | `columnClassName` | `(column: BoardItem) => string` | Column inner class | | `columnListContentClassName` | `(column: BoardItem) => string` | Column content class | | `cardWrapperClassName` | `string` | Card wrapper class | ### Performance & Behavior | Prop | Type | Description | | ---------------- | --------- | ---------------------------------------- | | `virtualization` | `boolean` | Enable virtual scrolling (default: true) | | `cardsGap` | `number` | Gap between cards in pixels | ### Event Handlers | Prop | Type | Description | | --------------- | --------------------- | --------------------- | | `onColumnClick` | `(e, column) => void` | Column click handler | | `onCardClick` | `(e, card) => void` | Card click handler | | `onScroll` | `(e, column) => void` | Column scroll handler | ## Data Structure ### BoardData ```typescript interface BoardData { root: BoardItem; [key: string]: BoardItem; } interface BoardItem { id: string; title: string; parentId: string | null; children: string[]; content?: any; // Your custom data type?: keyof ConfigMap; // Card type totalChildrenCount: number; isDraggable?: boolean; } ``` ### ConfigMap ```typescript type ConfigMap = { [type: string]: { render: (props: CardRenderProps) => React.ReactNode; isDraggable?: boolean; }; }; type CardRenderProps = { data: BoardItem; column: BoardItem; index: number; isDraggable: boolean; }; ``` ## Event Types ### CardMove Event ```typescript interface CardMove { cardId: string; fromColumnId: string; toColumnId: string; taskAbove: string | null; taskBelow: string | null; position: number; } ``` ### ColumnMove Event ```typescript interface ColumnMove { columnId: string; fromIndex: number; toIndex: number; } ``` ## CSS Classes The component provides CSS classes you can style: ```css /* Root container */ .rkk-board { } /* Column wrapper */ .rkk-column-outer { } /* Column inner container */ .rkk-column { } /* Column wrapper */ .rkk-column-wrapper { } /* Column header */ .rkk-column-header { } /* Column content area */ .rkk-column-content { } /* Column content list */ .rkk-column-content-list { } /* Card wrapper */ .rkk-generic-item-wrapper { } /* Card outer container */ .rkk-card-outer { } /* Card inner container */ .rkk-card-inner { } /* Drop shadow indicator */ .rkk-card-shadow { } /* Skeleton loading */ .rkk-skeleton { } ``` ## TypeScript Support This package is built with TypeScript and provides full type definitions. Import types as needed: ```typescript import { BoardData, BoardItem, ConfigMap, CardRenderProps, BoardProps, } from "react-kanban-kit"; ``` ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## License MIT © Hazem braiek