Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/IzumiSy/seizen-table/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Seizen Table provides 5 slot types where plugins can render components. Each slot serves a different purpose in the table layout.
type SlotType = "side-panel" | "header" | "footer" | "cell" | "inline-row";

side-panel

IDE-style vertical tab side panel on the left or right side of the table.

Use Cases

  • Navigation panels (column controls, filters)
  • Detail views (row details, inspectors)
  • Tool panels (bulk actions, export options)

Configuration

interface SidePanelSlot {
  position: "left-sider" | "right-sider";
  header?: string | ReactNode;
  render: () => ReactNode;
}
position
'left-sider' | 'right-sider'
required
Panel position relative to the table
header
string | ReactNode
Header content displayed at the top of the panel
render
() => ReactNode
required
Render function for panel content. Use usePluginArgs() inside to access configuration.

Example

import { definePlugin, usePluginContext } from "@izumisy/seizen-table/plugin";
import { z } from "zod";

function FilterPanel() {
  const { columns } = usePluginContext();
  return (
    <div style={{ padding: 16 }}>
      <h3>Filters</h3>
      {columns.map((col) => (
        <div key={col.key}>{col.header}</div>
      ))}
    </div>
  );
}

export const FilterPlugin = definePlugin({
  id: "filter",
  name: "Filter",
  args: z.object({ width: z.number().default(320) }),
  slots: {
    sidePanel: {
      position: "right-sider",
      header: "Filters",
      render: FilterPanel,
    },
  },
});

Visual Reference

┌────────────┬─────────────────────┬────────────┐
│ Left Panel │      Table          │ Right Panel│
│            │                     │            │
│ (optional) │  ┌──────────────┐   │ (optional) │
│            │  │ Table Header │   │            │
│            │  ├──────────────┤   │            │
│            │  │ Table Body   │   │            │
│            │  └──────────────┘   │            │
└────────────┴─────────────────────┴────────────┘

Renders between the table header and body rows.

Use Cases

  • Global search bars
  • Filter chips display
  • Action toolbars
  • Notification banners

Configuration

interface HeaderSlot {
  render: () => ReactNode;
}
render
() => ReactNode
required
Render function for header content. Use usePluginArgs() inside to access configuration.

Example

import { definePlugin, usePluginContext } from "@izumisy/seizen-table/plugin";
import { z } from "zod";

function GlobalSearchHeader() {
  const { table } = usePluginContext();

  return (
    <div style={{ padding: "8px 16px", borderBottom: "1px solid #e5e7eb" }}>
      <input
        type="text"
        placeholder="Search..."
        onChange={(e) => table.setGlobalFilter(e.target.value)}
        style={{ width: "100%" }}
      />
    </div>
  );
}

export const GlobalSearchPlugin = definePlugin({
  id: "global-search",
  name: "Global Search",
  args: z.object({}),
  slots: {
    header: {
      render: GlobalSearchHeader,
    },
  },
});

Visual Reference

┌─────────────────────────────┐
│      Table Header           │
├─────────────────────────────┤
│  Header Slot (your plugin)  │  ← Renders here
├─────────────────────────────┤
│      Table Body             │
│                             │
└─────────────────────────────┘

Renders below the table body.

Use Cases

  • Pagination controls
  • Summary statistics
  • Bulk action buttons
  • Status indicators

Configuration

interface FooterSlot {
  render: () => ReactNode;
}
render
() => ReactNode
required
Render function for footer content. Use usePluginArgs() inside to access configuration.

Example

import { definePlugin, usePluginContext } from "@izumisy/seizen-table/plugin";
import { z } from "zod";

function TableFooter() {
  const { data, selectedRows } = usePluginContext();

  return (
    <div style={{ padding: "8px 16px", borderTop: "1px solid #e5e7eb" }}>
      <p>
        {selectedRows.length} of {data.length} rows selected
      </p>
    </div>
  );
}

export const FooterPlugin = definePlugin({
  id: "footer",
  name: "Footer",
  args: z.object({}),
  slots: {
    footer: {
      render: TableFooter,
    },
  },
});

Visual Reference

┌─────────────────────────────┐
│      Table Header           │
├─────────────────────────────┤
│      Table Body             │
│                             │
├─────────────────────────────┤
│  Footer Slot (your plugin)  │  ← Renders here
└─────────────────────────────┘

cell

Custom cell renderer applied to all columns. First matching plugin wins.

Use Cases

  • Custom data type renderers (dates, currency, badges)
  • Conditional formatting
  • Interactive cell components
  • Cell-level actions

Configuration

interface CellSlot<TData = unknown> {
  render: (
    cell: Cell<TData, unknown>,
    column: Column<TData, unknown>,
    row: Row<TData>
  ) => ReactNode;
}
render
(cell, column, row) => ReactNode
required
Render function receiving TanStack Table primitives. Return null or undefined to fall through to the next plugin or default renderer.

Example: Custom Date Renderer

import { definePlugin } from "@izumisy/seizen-table/plugin";
import { z } from "zod";
import type { Cell, Column, Row } from "@tanstack/react-table";

function DateCellRenderer(
  cell: Cell<unknown, unknown>,
  column: Column<unknown, unknown>,
  row: Row<unknown>
) {
  const value = cell.getValue();
  const meta = column.columnDef.meta;

  // Only render if this is a date column
  if (meta?.filterType !== "date") return null;

  const date = value instanceof Date ? value : new Date(String(value));
  return <span>{date.toLocaleDateString()}</span>;
}

export const DateFormatterPlugin = definePlugin({
  id: "date-formatter",
  name: "Date Formatter",
  args: z.object({}),
  slots: {
    cell: {
      render: DateCellRenderer,
    },
  },
});

Example: Conditional Badge

function StatusCellRenderer(
  cell: Cell<unknown, unknown>,
  column: Column<unknown, unknown>
) {
  if (column.id !== "status") return null;

  const status = String(cell.getValue());
  const colors = {
    active: "green",
    inactive: "gray",
    pending: "yellow",
  };

  return (
    <span
      style={{
        padding: "4px 8px",
        borderRadius: "4px",
        backgroundColor: colors[status] || "gray",
        color: "white",
      }}
    >
      {status}
    </span>
  );
}

Fallthrough Behavior

Only the first plugin that returns a non-null value will render the cell. Return null or undefined to allow other plugins or the default renderer to handle the cell.
// ✅ Good: Returns null for non-matching columns
function MyRenderer(cell, column) {
  if (column.id !== "status") return null; // Falls through
  return <Badge value={cell.getValue()} />;
}

// ❌ Bad: Always renders, blocking other plugins
function MyRenderer(cell, column) {
  return <span>{String(cell.getValue())}</span>; // No fallthrough!
}

inline-row

Renders below a specific row when opened. First matching plugin wins.

Use Cases

  • Expandable row details
  • Nested tables
  • Related data display
  • Inline forms for editing

Configuration

interface InlineRowSlot<TData = unknown> {
  render: (row: Row<TData>) => ReactNode;
}
render
(row: Row<TData>) => ReactNode
required
Render function receiving the parent row. Return null or undefined to fall through to the next plugin.

Example: Row Expansion

import { definePlugin, usePluginContext } from "@izumisy/seizen-table/plugin";
import { z } from "zod";
import type { Row } from "@tanstack/react-table";

function InlineRowDetail(row: Row<unknown>) {
  const rowData = row.original as Record<string, unknown>;

  return (
    <div style={{ padding: 16, backgroundColor: "#f9fafb" }}>
      <h4>Details for Row {row.id}</h4>
      <pre>{JSON.stringify(rowData, null, 2)}</pre>
    </div>
  );
}

export const InlineDetailPlugin = definePlugin({
  id: "inline-detail",
  name: "Inline Detail",
  args: z.object({}),
  slots: {
    inlineRow: {
      render: InlineRowDetail,
    },
  },
});

Usage with Row Expansion

import { useSeizenTable } from "@izumisy/seizen-table";

function MyTable() {
  const table = useSeizenTable({
    data,
    columns,
    plugins: [InlineDetailPlugin.configure({})],
    enableRowExpansion: true, // Enable row expansion
  });

  return (
    <SeizenTable.Root table={table}>
      <SeizenTable.Content />
    </SeizenTable.Root>
  );
}

Visual Reference

┌─────────────────────────────┐
│  Row 1                      │
├─────────────────────────────┤  ← Row expanded
│  Inline Row Slot            │  ← Your plugin renders here
├─────────────────────────────┤
│  Row 2                      │
├─────────────────────────────┤
│  Row 3                      │
└─────────────────────────────┘

Slot Priority

When multiple plugins use the same slot:
  • side-panel: Only one plugin can occupy each position (left-sider or right-sider). The first plugin wins.
  • header/footer: All plugins render in order they appear in the plugins array.
  • cell/inline-row: First plugin that returns a non-null value wins. Return null to allow fallthrough.

Accessing Context in Slots

All slot render functions can use plugin context hooks:
import { usePluginContext, usePluginArgs } from "@izumisy/seizen-table/plugin";

function MySlotComponent() {
  // Access table instance, data, events
  const { table, data, selectedRows, useEvent } = usePluginContext();

  // Access validated plugin configuration
  const args = usePluginArgs<MyConfigType>();

  // Subscribe to events
  useEvent("selection-change", (rows) => {
    console.log("Selection changed", rows);
  });

  return <div>...</div>;
}