Skip to content
ilinxa/pro-ui

Task Card

alphav0.5.0

Schema-driven task card with time-aware color coding, popup and inline editing, clipboard and drag-drop payloads, and permission predicates.

Category: Data DisplayUpdated: 2026-08-11Created: 2026-05-20Author: ilinxa

Context

Task Card is a time-aware task renderer for workflow apps, content schedulers, and agent run-queues. It carries the standard task fields (name, description, status, active, target/creator person, multi-image, multi-link, dates) and paints urgency onto the card border via an OKLCH ramp (green → red, urgency direction; past expireAt pins full red). The engine accepts setAt + startAt + expireAt OR duration with deterministic precedence (expireAt wins). Two edit modes ship in v0.1: a popup dialog form (default) and an inline-toggle mode (editable=true) where fields swap to inputs in place. JSON I/O mirrors card-tree patterns — controlled (value) or uncontrolled (defaultValue), 12-method imperative handle (getValue / getTree / isDirty / markClean / focusItem / copy / paste / setBorderColor / toggleActive / setLocked / openEdit / closeEdit), clipboard via application/x-ilinxa-task+json MIME + Cmd/Ctrl+C/V, HTML5 DnD with the same payload. Granular events (onFieldEdited / onStatusChanged / onItemAdded / onItemRemoved / onItemMoved / onColorOverridden / onActiveToggled / onLockedToggled / onCopy / onPaste / onChange) cover every mutation. Permissions mirror card-tree: declarative permissions matrix (default / byLevel / byItem with inherit cascade) + six per-action predicates + onPermissionDenied for analytics. Companion: ships taskCardKanbanRenderer for kanban-board with dragHandle:'shell' (v0.3 — the whole card is the drag activator; the adapter passes canDragItem={()=>false} to disable the card's own HTML5 drag so the two DnD systems don't fight). Per-card collapsibility (chevron in header) hides body + nested children to compact deep trees. v0.3 adds: a two-line header so the title never truncates to nothing; an editable status badge that becomes a status-change dropdown (statusEditable, default true); status tones (TaskStatusOption.tone done/blocked → gray border + dimmed overlay + auto-collapse) that decouple the status signal from the time-driven urgency border; and a priority + labels model extension (TaskItem.priority / labels with priorityOptions / labelOptions) rendered as a header meta row. Color override surfaces as a centered Dialog with palette swatches + custom CSS color input + an Auto button that clears the override and hands control back to the time engine.

Installation

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Register the @ilinxa namespace (once per project)Add to your components.json. Merge with existing config.
"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}
Install the component
pnpm dlx shadcn@latest add @ilinxa/task-card

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/task-card-fixtures

Preview

Ramp:

Demo clock frozen at 2026-05-20T12:00:00.000Z so the border colors stay deterministic.

Fresh task — early ramp (green)

In progress

Draft the OKR for Q3

Outline goals, key results, and timelines for the next quarter.

Due in 5 days
HEHessamLTLeadership team(creator)

Urgent task — late ramp (orange/red)

In progress

Review pull request #482

Auth middleware rewrite — flagged by legal compliance.

Due in 4 hours
HEHessamLBLegal Compliance Bot(creator)

Overdue — pinned full red, with images

Blocked

Publish Q1 retrospective

Past expireAt — engine pins the border to full red.

Due 2 days ago
AMAmir

Nested 3-level family — independent per-item dates, locked child, custom override

In progress

Ship the new design system

Three-level nested example. Each child has its own dates.

Due next week
HEHessam
Done

Color tokens

Due yesterday
In progress

Typography

Due in 3 days
To do

Pick body font

Duration: 2h
To do

Lock heading scale

Due in 2 days
To do

Component playground

Due next week

v0.3 — priority & labels meta row + status tones

The header gains a priority badge + label chips (border/text color only). The done / blocked statuses carry a tone so terminal cards get a gray border, a dimmed overlay, and auto-collapse. Turn on editableabove, then use the status badge (or the overlay's badge) to change status — changing a terminal status clears the overlay.

In progress

Migrate auth to the new session service

HighFrontendSecurityQ3

Priority + labels surface in the header meta row.

Due in 2 days
HEHessam
Done

Wire up the analytics dashboard

Normal
Done
Blocked

Deploy to the EU region

UrgentSecurity
Blocked

4 demos · click the pencil icon (or the action menu) to edit · Cmd/Ctrl+C / Cmd/Ctrl+V on a focused card copies / pastes as child · drag a card onto another's children area to paste.

Inside kanban-board

The same TaskCard slots into a kanban column via the exported taskCardKanbanRenderer. Grab a card anywhere to drag it across columns and drop it anywhere in a column; the card body keeps full edit / collapse semantics (renderer's dragHandle:"shell" + canDragItem disabled so the two drag systems don't fight).

To do1
in-progress

Draft the OKR for Q3

Outline goals, key results, and timelines for the next quarter.

Due 3 months ago
HEHessamLTLeadership team(creator)
In progress2
in-progress

Review pull request #482

Auth middleware rewrite — flagged by legal compliance.

Due 3 months ago
HEHessamLBLegal Compliance Bot(creator)
Demo note

Note + todo renderers co-exist on the same board. Grab a todo card anywhere to drag it (shell-drag), and drop it anywhere in a column. The card body keeps full edit / collapse semantics.

Done1
blocked

Publish Q1 retrospective

Past expireAt — engine pins the border to full red.

Due 3 months ago
AMAmir

Demo source

demo.tsxtsx

Usage

When to use

Reach for TaskCard when you need a time-aware task surface — agent run queues, content schedules, sprint cards, workflow tasks. The card carries the standard task fields out of the box, paints urgency directly onto the chrome via the time engine, and is fully data-interactive (JSON I/O, clipboard, drag-drop).

For lightweight tree-row rendering (collapsible sub-items, inline checkbox, single-line description preview), reach for the sibling task-tree procomp — shares the same TaskItem schema so drags + clipboard payload work cross-procomp. For kanban, use kanban-board + the renderer exported below.

Basic example

import { TaskCard } from "@/components/task-card";

export function Example() {
  return (
    <TaskCard
      defaultValue={{
        id: "task-001",
        name: "Review pull request #482",
        status: "in-progress",
        active: true,
        setAt: "2026-05-19T09:00:00Z",
        expireAt: "2026-05-21T17:00:00Z",
      }}
      editable
    />
  );
}

Auto-color engine

The engine normalizes (now − startAt) / (expireAt − startAt) to [0, 1] and maps it through an OKLCH ramp — green at 0, red at 1. Past expireAt pins to full red. If expireAt is absent, duration is used instead. Per-item borderColor overrides the engine for that node. Set colorRamp to swap presets: 'default' | 'muted' | 'vivid' | 'monochrome' — or pass a custom function for full control.

Edit modes

  • editable=false (default) → click the edit button to open a popup with all fields in one form.
  • editable=true→ click the edit button to toggle inline editing on that item. A secondary "Edit in dialog…" icon and action-menu item still open the popup.
  • locked=true on an item blocks both modes for that node. onPermissionDenied fires with reason: 'locked'.

Collapsibility

Every card has a chevron at the left of its header. Clicking it collapses the card's body (description, time-info, chips, links, images) andits nested children — only the header stays visible. Per-item, independent at every depth. The collapse flag is UI-only state in the component's reducer (collapsedIds: ReadonlySet<string>) — it is not stored on TaskItem, so JSON I/O round-trips ignore it. Remounting via key resets all collapse state.

Color override (Dialog)

Click the action menu (...) on any card and choose Override color… to open a centered Dialog: 8 curated OKLCH palette swatches, a free-text input for any CSS color string, and an Autobutton (with sparkle icon) that clears the override and hands rendering back to the time engine. Dialog (vs Popover) avoids an outside-click race with the action menu's dismissal.

JSON I/O and clipboard

Use the imperative ref handle to copy(), paste(), getValue(), or getTree(). When a card has keyboard focus, Cmd/Ctrl+C copies it and Cmd/Ctrl+V pastes from the clipboard as a child. Drag a card onto another card's children area to drop the JSON payload as a child. The clipboard uses MIME application/x-ilinxa-task+json with a text/plain JSON fallback for browsers without custom-MIME support.

SSR + time engine

The engine reads new Date() at render time, which means server- and client-rendered HTML can compute slightly different border colors when nowisn't frozen. The card mitigates this with suppressHydrationWarning on the article root — the visual flash is sub-frame and the data is consistent server→client. For deterministic SSR (snapshot tests, server-rendered HTML caching), pass a frozen now: now={new Date("2026-05-20T12:00:00Z")}.

Composing with kanban-board

The package exports taskCardKanbanRenderer — a typed KanbanCardRenderer<TaskItem> with dragHandle: "header" so the kanban grip strip handles outer column reorder while the card body keeps its full edit / collapse / nested-DnD semantics. Drop it into the KanbanBoard renderers array alongside (or instead of) the built-in card renderers. The kanban-board demo on its own detail page shows mixed renderers; below is the minimum wiring.

import { KanbanBoard, kanbanCardRenderer } from "@/components/kanban-board";
import { taskCardKanbanRenderer } from "@/components/task-card";
import type { KanbanData } from "@/components/kanban-board";

const initial: KanbanData = {
  swimlanes: [],
  columns: [
    {
      id: "col-todo",
      title: "To do",
      color: "slate",
      items: [
        {
          id: "kanban-task-1",
          rendererId: "task-card",            // matches renderer.id
          data: {
            id: "task-001",
            name: "Review PR #482",
            status: "todo",
            active: true,
            setAt: "2026-05-19T09:00:00Z",
            expireAt: "2026-05-21T17:00:00Z",
          },
        },
      ],
    },
    { id: "col-doing", title: "In progress", color: "lime", items: [] },
    { id: "col-done",  title: "Done",        color: "emerald", items: [] },
  ],
};

export function TaskKanbanExample() {
  return (
    <KanbanBoard
      renderers={[kanbanCardRenderer, taskCardKanbanRenderer]}
      defaultData={initial}
      aria-label="Task kanban"
    />
  );
}

rendererId: "task-card" on each kanban item must match taskCardKanbanRenderer.id — the board uses this to look up the renderer for each item. Items with no matching renderer show a placeholder card.

Other composition targets

  • flow-canvas (via todo-card-tree-node sibling adapter, shipping separately): exposes a NodeRenderer that wraps this card.
  • task-tree (sibling procomp, shipping separately): lightweight tree-row alternative that shares the same TaskItem schema and DnD payload so drags cross-procomp.

Gotchas

  • Images and links are read-only in inline edit mode in v0.1 — use the popup to add / remove them.
  • No undo/redo in v0.1. Wire optimistic-undo yourself or wait for v0.2+.
  • One item edits at a time. Opening a new edit closes any open one.

Features

  • Fixed TaskItem schema (closed; consumer-defined status enum)
  • Time-driven OKLCH border-color engine (green → red urgency ramp, four named presets + custom-fn escape hatch)
  • Single root setInterval for color refresh (default 60s; configurable; 0 disables) — never per-card
  • Two edit modes in v0.1: popup dialog (default) and inline-toggle (editable=true) — both share the same field→primitive mapping
  • Per-item borderColor override skips the engine; per-host colorRamp swaps the ramp globally
  • Per-item lock blocks edit + drag; onLockedToggled fires on changes
  • Multi-image + multi-link support with sensible default renderers (slot props for v0.2)
  • Controlled (`value`) + uncontrolled (`defaultValue`) modes — onChange reports edits; controlled value reconciles with an echo guard
  • JSON I/O mirroring card-tree v0.3 — defaultValue + imperative handle with getValue / getTree / isDirty / markClean / focusItem / copy / paste / setBorderColor / toggleActive / setLocked / openEdit / closeEdit
  • Clipboard ops via the unified cross-surface task envelope (v0.4 — shared lib/clipboard.ts: ilinxa/task envelope on application/x-ilinxa-task+json MIME + text/plain fallback) + Cmd/Ctrl+C / Cmd/Ctrl+V — a card copied here pastes into gantt / calendar / tree and vice-versa
  • HTML5 DnD card-as-source + card-as-children-drop-target with the same payload
  • Granular event surface — 11 typed events covering every mutation (mirrors card-tree)
  • Permission matrix: declarative permissions {default, byLevel, byItem, inherit} + 6 per-action predicates + onPermissionDenied with typed reason
  • Infinite recursive nesting; children carry the same action affordances as parents
  • Per-card collapsibility — chevron in the header toggles body + children visibility (UI-only state; not part of TaskItem schema)
  • Two-line header (v0.3) — title wraps up to 3 lines and never truncates to nothing, even in a narrow kanban column
  • Editable status badge (v0.3) — becomes a status-change dropdown when editable (statusEditable, default true) reusing the inline editor's reducer action + events
  • Status tones (v0.3) — TaskStatusOption.tone done/blocked gets a gray border + dimmed overlay + auto-collapse, decoupling status from the time-driven urgency border
  • Priority + labels (v0.3) — optional TaskItem.priority / labels with priorityOptions / labelOptions render a header meta row (border/text color only; renders nothing when unset)
  • Color override via centered Dialog (palette swatches + custom CSS color input + Auto button to hand control back to the engine)
  • ARIA: role='region' root + role='article' per card + role='group' for children; full a11y on edit affordances
  • SSR hydration mitigated via `suppressHydrationWarning` on the article root (sub-frame visual flash; data consistent server→client); pass frozen `now` for deterministic SSR
  • Companion: taskCardKanbanRenderer named export for kanban-board with dragHandle:'shell' (v0.3) + canDragItem disabled so the card and board DnD don't fight

Tags

todotaskcard-treetime-awareauto-colordrag-dropclipboardpermissionskanban-rendererdata

Dependencies

shadcn primitives: avatar, badge, button, dialog, dropdown-menu, input, label, scroll-area, select, separator, switch, textarea, tooltip
npm peer deps: lucide-react@^1.11.0