Skip to content

Latest commit

 

History

History
433 lines (355 loc) · 10.5 KB

File metadata and controls

433 lines (355 loc) · 10.5 KB
title Plugin Kanban

import { InteractiveDemo } from '@/app/components/InteractiveDemo'; import { PluginLoader } from '@/app/components/PluginLoader';

Kanban board component with drag-and-drop powered by @dnd-kit.

Installation

npm install @object-ui/plugin-kanban

This package publishes a stylesheet. Import it after the base sheets, or the board renders unstyled — the themed utilities it uses have no other source in a published app (#4929):

/* src/index.css */
@import "tailwindcss";
@import "@object-ui/components/style.css";
@import "@object-ui/fields/style.css";
@import "@object-ui/plugin-kanban/style.css";

<PluginLoader plugins={['kanban']}>

Interactive Examples

Usage

Basic Usage

// Import once in your app entry point
import '@object-ui/plugin-kanban'
import type { KanbanSchema } from '@object-ui/plugin-kanban'

// Use in schemas
const schema: KanbanSchema = {
  type: 'kanban',
  columns: [
    {
      id: 'todo',
      title: 'To Do',
      cards: [
        { id: '1', title: 'Task 1', description: 'Do something' }
      ]
    },
    {
      id: 'done',
      title: 'Done',
      cards: []
    }
  ],
  onCardMove: (cardId, fromCol, toCol, index) => {
    console.log(`Card ${cardId} moved`)
  }
}

Features

  • Drag and drop cards between columns
  • Column limits (WIP limits)
  • Card badges for status/priority
  • Keyboard navigation
  • Lazy-loaded (~100-150 KB loads only when rendered)

Schema API

{
  type: 'kanban',
  columns?: KanbanColumn[],
  onCardMove?: (cardId, fromColumnId, toColumnId, newIndex) => void,
  className?: string
}

interface KanbanColumn {
  id: string
  title: string
  cards: KanbanCard[]
  limit?: number              // Max cards allowed
  className?: string
}

interface KanbanCard {
  id: string
  title: string
  description?: string
  badges?: Array<{
    label: string
    variant?: 'default' | 'secondary' | 'destructive' | 'outline'
  }>
}

Properties

Property Type Description
columns KanbanColumn[] Array of column definitions
limit number Rows fetched by an object-driven board (default 100)
onCardMove function Callback when a card is moved
className string Additional Tailwind CSS classes

Column Properties

Property Type Description
id string Unique column identifier
title string Column title
cards KanbanCard[] Cards in this column
limit number Max cards allowed (WIP limit)
className string Additional Tailwind CSS classes

Card Properties

Property Type Description
id string Unique card identifier
title string Card title
description string Card description
badges Badge[] Status/priority badges

Card fields (object-driven boards)

When a board is bound to an object (an object-kanban, or a kanban view inside ListView / ObjectView) instead of being given static columns, the fields rendered on each card resolve in priority order:

  1. View-level cardFields — the fields the view configures (a kanban view's columns, forwarded as cardFields). An explicit choice always wins.
  2. The object's highlightFields — the object's ADR-0085 semantic role (its curated "most important fields"), the same list the Grid, List and Detail surfaces already default to. Used when the view configures no card fields, so a board over an object with no per-view config still surfaces meaningful fields. Entries that reference a field the object no longer declares are ignored.
  3. A legacy semantic-field heuristic — a best-effort guess (amount, owner, priority, …) used only when neither of the above is available.

Defaulting to highlightFields keeps a card's contents consistent with the object's other views without every kanban view having to re-declare its fields.

Row cap (object-driven boards)

An object-driven board fetches at most limit records, defaulting to 100. A board renders every fetched record into a lane and offers no pagination control, so this is the author's window on the object rather than a page size:

import type { ObjectKanbanSchema } from '@object-ui/types'

const board: ObjectKanbanSchema = {
  type: 'object-kanban',
  objectName: 'opportunity',
  groupBy: 'stage',
  limit: 250,          // rows fetched; omit for the default 100
}

The dataSource binding sets it too — its own limit, or the pagination.pageSize of the view it names (see Data source).

This is the **board's** `limit`, not a column's. `limit` on a *column* is that lane's WIP limit — the card count at which the lane warns — and has no effect on the query.

Examples

Project Task Board

import type { KanbanSchema } from '@object-ui/plugin-kanban'

const taskBoard: KanbanSchema = {
  type: 'kanban',
  columns: [
    {
      id: 'backlog',
      title: 'Backlog',
      cards: [
        {
          id: 'task-1',
          title: 'Design new homepage',
          description: 'Create mockups for the new landing page',
          badges: [
            { label: 'Design', variant: 'default' },
            { label: 'High Priority', variant: 'destructive' }
          ]
        }
      ]
    },
    {
      id: 'in-progress',
      title: 'In Progress',
      limit: 3,  // WIP limit
      cards: [
        {
          id: 'task-2',
          title: 'Implement authentication',
          description: 'Add OAuth2 login flow',
          badges: [
            { label: 'Backend', variant: 'secondary' }
          ]
        }
      ]
    },
    {
      id: 'review',
      title: 'Code Review',
      cards: []
    },
    {
      id: 'done',
      title: 'Done',
      cards: []
    }
  ],
  onCardMove: (cardId, fromCol, toCol, index) => {
    // Update backend/state
    console.log(`Moved ${cardId} from ${fromCol} to ${toCol}`)
  }
}

Support Ticket Board

import type { KanbanSchema } from '@object-ui/plugin-kanban'

const ticketBoard: KanbanSchema = {
  type: 'kanban',
  columns: [
    {
      id: 'new',
      title: 'New Tickets',
      cards: [
        {
          id: 'ticket-1',
          title: 'Login not working',
          description: 'User cannot log in with Google',
          badges: [
            { label: 'Bug', variant: 'destructive' },
            { label: 'P1', variant: 'destructive' }
          ]
        }
      ]
    },
    {
      id: 'assigned',
      title: 'Assigned',
      limit: 5,
      cards: []
    },
    {
      id: 'resolved',
      title: 'Resolved',
      cards: []
    }
  ],
  className: 'min-h-[600px]'
}

Sales Pipeline

import type { KanbanSchema } from '@object-ui/plugin-kanban'

const salesPipeline: KanbanSchema = {
  type: 'kanban',
  columns: [
    {
      id: 'leads',
      title: 'Leads',
      cards: [
        {
          id: 'lead-1',
          title: 'Acme Corp',
          description: '$50,000 - Enterprise plan',
          badges: [
            { label: 'Hot Lead', variant: 'destructive' }
          ]
        }
      ]
    },
    {
      id: 'qualified',
      title: 'Qualified',
      cards: []
    },
    {
      id: 'proposal',
      title: 'Proposal Sent',
      cards: []
    },
    {
      id: 'won',
      title: 'Won',
      cards: []
    }
  ]
}

Card Badges

Use badges to show card status, priority, or categories:

import type { KanbanCard } from '@object-ui/plugin-kanban'

const card: KanbanCard = {
  id: 'task-1',
  title: 'Important Task',
  badges: [
    { label: 'Frontend', variant: 'default' },
    { label: 'Urgent', variant: 'destructive' },
    { label: 'Reviewed', variant: 'secondary' },
    { label: 'Blocked', variant: 'outline' }
  ]
}

Badge Variants

  • default - Blue badge
  • secondary - Gray badge
  • destructive - Red badge
  • outline - Outlined badge

Column Limits (WIP Limits)

Set maximum cards per column to enforce work-in-progress limits:

import type { KanbanColumn } from '@object-ui/plugin-kanban'

const column: KanbanColumn = {
  id: 'in-progress',
  title: 'In Progress',
  limit: 3,  // Max 3 cards
  cards: [
    { id: 'task-2', title: 'Implement authentication' }
  ]
}

When the limit is reached, the column shows visual feedback.

Event Handling

Handle card movements to update your backend or state:

import { useState } from 'react'
import type { KanbanColumn, KanbanSchema } from '@object-ui/plugin-kanban'

declare function updateCardColumn(cardId: string, toColumnId: string, newIndex: number): Promise<void>
declare function moveCard(columns: KanbanColumn[], cardId: string, toColumnId: string, newIndex: number): KanbanColumn[]

export function useBoard(initialColumns: KanbanColumn[]) {
  const [columns, setColumns] = useState<KanbanColumn[]>(initialColumns)

  const schema: KanbanSchema = {
    type: 'kanban',
    columns,
    // `onCardMove` returns void, so the callback cannot be awaited by the board:
    // start the write and update local state without blocking the drop.
    onCardMove: (cardId, fromColumnId, toColumnId, newIndex) => {
      // Update database
      void updateCardColumn(cardId, toColumnId, newIndex)

      // Update local state
      setColumns((prev) => moveCard(prev, cardId, toColumnId, newIndex))
    },
  }

  return schema
}

Bundle Size

The plugin uses lazy loading to optimize bundle size:

  • Initial load: ~0.2 KB (entry point)
  • Lazy chunk: ~100-150 KB (loaded when kanban is rendered)
  • Includes @dnd-kit for drag-and-drop functionality

Accessibility

The kanban board includes:

  • Keyboard navigation - Move cards with keyboard
  • Screen reader support - ARIA labels for all interactions
  • Focus management - Clear focus indicators

TypeScript Support

import type { KanbanSchema, KanbanCard, KanbanColumn } from '@object-ui/plugin-kanban'

const column: KanbanColumn = {
  id: 'todo',
  title: 'To Do',
  cards: [],
  limit: 5
}

const kanbanSchema: KanbanSchema = {
  type: 'kanban',
  columns: [column]
}

Related Documentation