npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tc96/collection-views

v0.4.0

Published

Domain-neutral collection views for React applications using COSS primitives and DnD Kit.

Readme

Collection Views

A domain-neutral collection view package for React applications built with COSS primitives, DnD Kit, and Tailwind CSS. Version 0.4.0 publishes the package as @tc96/collection-views. The repository remains named kanban for now, and the existing Kanban* component names remain available alongside List* components.

The package provides the interaction and presentation layer for SaaS board views. Your application remains responsible for business stages, permissions, remote state, mutations, navigation, and persistence.

Highlights

  • generic typed columns and consumer-rendered cards
  • collection provider with controlled or uncontrolled view preferences
  • grouped List view with Status and Assignee projections
  • accessible card skeleton for consumer loading states
  • consumer-controlled full and compact card presentations
  • accessible pointer and keyboard drag-and-drop with the current DnD Kit React API
  • native optimistic sorting within and between populated columns
  • consumer-controlled move acceptance and rollback
  • read-only mode when onMoveCard is omitted
  • responsive desktop board and mobile stage selector
  • horizontal desktop scrolling by clicking, holding, and dragging the board
  • horizontal scrollbar visible only while that board-drag gesture is active
  • minimal drag motion with DnD Kit DragOverlay, feedback, and accessibility plugins
  • COSS-first visual primitives with no direct Base UI usage in the Kanban pattern

Requirements

  • React 19
  • Base UI 1.x
  • Lucide React 1.x
  • Tailwind CSS 4

The package is COSS-first. Every Kanban component imports visual primitives from @/components/ui/*; only the copy-owned COSS source in src/components/ui depends on its Base UI internals. The source registry does not copy those files into the pattern. It declares the official COSS registry dependencies so the consumer receives them through its configured ui alias.

The views compose the canonical COSS Badge, Button, Card, Collapsible, Empty, Menu, ScrollArea, Skeleton, Toolbar, and Tooltip primitives. The npm build bundles their copy-owned source. The source registry instead declares the matching @coss/* items, installing them at @/components/ui/* through the consumer's ui alias.

Installation

Install the published package with your package manager:

pnpm add @tc96/collection-views
# or: bun add @tc96/collection-views
# or: npm install @tc96/collection-views

The consumer must already provide the peer dependencies:

pnpm add react react-dom @base-ui/react lucide-react tailwindcss

Migration from 0.3.x

Update the dependency and registry path:

bun remove @tc96/kanban
bun add @tc96/collection-views
bunx shadcn@latest add ./node_modules/@tc96/collection-views/registry/collection-views.json

Kanban* exports remain named after the Kanban view, while all source files are installed under the new patterns/collection-views namespace.

COSS source location

@tc96/collection-views supports two installation modes. Use the npm package when you want to import it from node_modules. Use the bundled registry item when you want the source copied into the consuming app.

Keep ui mapped to your primitive components and add patterns for TC96 patterns. patterns is not a replacement for ui:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "tsx": true,
  "aliases": {
    "components": "@/components",
    "ui": "@/components/ui",
    "patterns": "@/components/patterns"
  }
}

After installing the package, add the local registry item:

bun add @tc96/collection-views
bunx shadcn@latest add ./node_modules/@tc96/collection-views/registry/collection-views.json

The registry item targets @components/patterns/collection-views and declares every required @coss/* primitive as a registry dependency, so the shadcn CLI installs visual primitives through aliases.ui while the package exposes both Kanban and List.

Tailwind must scan the installed package and your theme must expose the standard COSS semantic tokens. The official COSS styling preset also provides --animate-skeleton and its keyframes:

@import "tailwindcss";
@source "../node_modules/@tc96/collection-views/dist";

Quick start

import {
  KanbanCard,
  KanbanCardHeader,
  KanbanCardTitle,
  KanbanView,
  type KanbanCardMove,
  type KanbanColumnData,
} from "@tc96/collection-views";

type RecordItem = {
  id: string;
  title: string;
};

export function Board({
  columns,
  moveRecord,
  openCreateForm,
  openColumnSettings,
}: {
  columns: KanbanColumnData<RecordItem>[];
  moveRecord: (move: KanbanCardMove<RecordItem>) => boolean | Promise<boolean>;
  openCreateForm: (columnId: string) => void;
  openColumnSettings: (columnId: string) => void;
}) {
  return (
    <KanbanView
      columns={columns}
      getCardLabel={(record) => record.title}
      getColumnActions={(column) =>
        column.id === "backlog"
          ? {
              onAddCard: (columnId) => openCreateForm(columnId),
              onOpenSettings: (columnId) => openColumnSettings(columnId),
            }
          : undefined
      }
      getKey={(record) => record.id}
      onMoveCard={moveRecord}
      renderCard={(record) => (
        <KanbanCard>
          <KanbanCardHeader>
            <KanbanCardTitle className="text-sm">{record.title}</KanbanCardTitle>
          </KanbanCardHeader>
        </KanbanCard>
      )}
    />
  );
}

KanbanCard preserves the COSS card composition without exposing the consumer to the underlying UI package. Render KanbanCardHeader, KanbanCardContent, and KanbanCardFooter directly under it as needed. Use the content for labels and other task data; reserve the footer for metadata such as assignee or date. Consumers may adjust typography on KanbanCardTitle or KanbanCardDescription, while the sections retain their spacing and structure.

Collection provider and List

The 0.4.0 collection API accepts consumer models through adapters. Status and assignee are required capabilities, but the package does not require properties with those names:

import {
  CollectionProvider,
  CollectionSettingsMenu,
  CollectionToolbar,
  CollectionToolbarGroup,
  CollectionViewOutlet,
  ListItem,
  ListItemHeader,
  ListItemTitle,
  ListView,
} from "@tc96/collection-views";

export function TaskList({ tasks, statuses, assignees }: Props) {
  return (
    <CollectionProvider
      collection={{
        assignees,
        getAssigneeId: (task) => task.ownerId,
        getKey: (task) => task.id,
        getLabel: (task) => task.title,
        getStatusId: (task) => task.stageId,
        items: tasks,
        statuses,
      }}
      defaultPreferences={{ groupBy: "status", view: "list" }}
    >
      {({ collection }) => (
        <>
          <CollectionToolbar>
            <CollectionToolbarGroup>
              <CollectionSettingsMenu />
            </CollectionToolbarGroup>
          </CollectionToolbar>
          <CollectionViewOutlet
            collection={collection}
            renderKanbanItem={(task) => <TaskCard task={task} />}
            renderListItem={(task) => (
              <ListItem aria-label={task.title}>
                <ListItemHeader>
                  <ListItemTitle>{task.title}</ListItemTitle>
                </ListItemHeader>
              </ListItem>
            )}
          />
        </>
      )}
    </CollectionProvider>
  );
}

CollectionSettingsMenu exposes View → Grid | List and Grouping by → Status | Assignee. Both controls update the same provider preferences synchronously. The provider supports preferences/onPreferencesChange for controlled persistence and defaultPreferences for local state. The package never writes to storage, URL state, or a remote cache.

Card display modes

KanbanCard defaults to the existing full presentation. Set its controlled display prop to compact to retain only the title, compact metadata, and date. The consumer owns the control and persistence of this preference; keep the control outside draggable cards, such as in the board toolbar settings:

import {
  KanbanCard,
  KanbanCardContent,
  KanbanCardDescription,
  KanbanCardFooter,
  KanbanCardHeader,
  KanbanCardTitle,
  type KanbanCardDisplay,
} from "@tc96/collection-views";

export function TaskCard({
  display,
  task,
}: {
  display: KanbanCardDisplay;
  task: Task;
}) {
  return (
    <KanbanCard display={display}>
      <KanbanCardHeader>
        <KanbanCardTitle>{task.title}</KanbanCardTitle>
        <KanbanCardDescription>{task.description}</KanbanCardDescription>
        {display === "compact" ? <TaskCompactMetadata task={task} /> : null}
      </KanbanCardHeader>
      <KanbanCardContent>{/* Full tags */}</KanbanCardContent>
      <KanbanCardFooter>{/* Assignee and date */}</KanbanCardFooter>
    </KanbanCard>
  );
}

Compact metadata is consumer-owned composition. The toolbar in Storybook demonstrates a board-wide Detailed or Compact setting under Settings → Layout → Cards. The same Layout group includes consumer-owned mocks for column width, swimlane grouping, and expanded-column visibility.

getColumnActions enables the settings and add controls independently for each column. The package renders accessible icon buttons and reports the logical column id; the consumer owns permissions, menu or form content, mutations, and persistence. Omit it, return undefined, or omit either callback to keep that action out of the column header. Use settingsLabel and addLabel when the default Portuguese accessible labels do not match the product locale.

Omit onMoveCard to render a read-only board. Returning or resolving false rejects the interaction and restores the previous view. A rejected optimistic mutation must also restore the consumer-owned cache.

onMoveCard is also called when a card changes position inside its current column. In that case, sourceColumnId and targetColumnId are equal while sourceIndex and targetIndex describe the requested reorder. Apply and persist that ordering in the consumer, update columns, and return true to keep the preview. For asynchronous persistence, return Promise<boolean>. While it is pending, the requested order stays visible even if columns temporarily receives an older cache snapshot. Resolve true after the canonical data accepts the order, or roll the consumer cache back and resolve false when persistence fails.

The board uses DragDropProvider, useSortable, sortable group/index, the native OptimisticSortingPlugin, and the official move() helper. The plugin reorders the DOM during sortable drag-over without forcing a React list render. The current DragOverlay source callback renders the moving card without package-owned active-card state, leaving the original sortable element free to occupy the optimistic slot. React state is used only when a plain column droppable is needed to enter an empty list and after drop to shield the optimistic order from stale consumer snapshots. There is no package-owned before/after collision algorithm.

Loading cards

Use KanbanCardSkeleton while card data is unavailable. It preserves the card surface without creating placeholder domain records or enabling drag behavior:

import { KanbanCardSkeleton } from "@tc96/collection-views";

export function LoadingCard() {
  return <KanbanCardSkeleton label="Loading task" />;
}

The skeleton exposes role="status" and aria-busy="true". Its label is announced by assistive technology while its visual placeholders remain decorative. Its visual primitive is the COSS ui/skeleton; the Kanban package only composes its card-specific geometry and loading semantics.

Long consumer content

Kanban columns, sortable wrappers, and cards clamp their intrinsic width so a long unbroken value cannot resize a column or the board. The sortable wrapper does not clip overflow, keeping tooltips, menus, focus halos, and shadows under consumer control. The card clips visual overflow as a final layout safeguard, which the consumer can override when needed. The consumer still owns how a tag is presented and made fully available—for example, combine max-w-full, truncate, and a title or tooltip containing the complete label. The Card story demonstrates that strategy with a long tag; wrapping is equally valid when it better matches the product.

For sortable cards with an interactive action, the first button or link is also the keyboard drag activator. Press Space to drag or Enter to keep the control's normal action without adding a second Tab stop.

Use @tc96/collection-views/core when a non-visual layer only needs the public types and the DnD Kit event-to-Kanban projection helpers.

Ownership boundary

| Package owns | Consumer owns | | --- | --- | | board layout and responsive stage navigation | business meaning and order of stages | | DnD Kit sensor configuration and native sortable preview | permission to move a specific card | | stable visual card and column identities | server state and canonical ordering | | visual reconciliation while a move is pending | mutations, optimistic updates, rollback, and errors | | presentation-level move callback | workflows, automation, and persistence |

The package reports both cross-column moves and same-column reorders through onMoveCard. The consumer remains responsible for its ordering rules and for persisting the requested targetIndex.

Development

Use Bun 1.3.14 and Node 24.18.0.

bun install
bun run storybook
bun run lint:ci
bun run typecheck
bun test
bun run test:storybook
bun run build

See CONTRIBUTING.md for the contribution workflow and ADR-001 for the adapter decision.

License

MIT © Gabriel Melo.