{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-grid",
  "type": "registry:block",
  "title": "Invoices data grid",
  "description": "A virtualized data grid wired to sample invoices: search, column filters, grouping, totals, an editable amount that is validated, range selection with copy and paste, and CSV/XLSX export.",
  "author": "Box Kite <https://www.box-kite.dev>",
  "categories": [
    "data-grid",
    "table",
    "dashboard"
  ],
  "dependencies": [
    "@box-kite/react"
  ],
  "files": [
    {
      "path": "registry/blocks/data-grid/invoices-grid.tsx",
      "content": "'use client';\nimport Box from '@box-kite/react';\nimport DataGrid, { type CellModel, type GridDefinition } from '@box-kite/react/components/dataGrid';\nimport Flex from '@box-kite/react/components/flex';\nimport { H2, P } from '@box-kite/react/components/semantics';\nimport { type Invoice, invoices } from './invoice-rows';\n\n/**\n * An invoices table with what a real one needs: search, per-column filters, grouping from the column\n * menu, totals, an editable amount that is validated, a block of cells to copy, and CSV/XLSX export.\n *\n * None of it is configuration to keep in sync — the grid is one `def` object. Swap `invoices` for your\n * own rows and edit the columns.\n */\nconst money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });\n\nconst STATUS_STYLES = {\n  paid: { bgColor: 'emerald-100', color: 'emerald-700', theme: { dark: { bgColor: 'emerald-950', color: 'emerald-300' } } },\n  pending: { bgColor: 'amber-100', color: 'amber-700', theme: { dark: { bgColor: 'amber-950', color: 'amber-300' } } },\n  overdue: { bgColor: 'rose-100', color: 'rose-700', theme: { dark: { bgColor: 'rose-950', color: 'rose-300' } } },\n} as const;\n\n// A cell renderer is a component defined outside the render: a new identity on every render remounts\n// the whole column on every scroll.\nfunction StatusCell({ cell }: { cell: CellModel<Invoice> }) {\n  const status = cell.value as Invoice['status'];\n\n  return (\n    <Flex ai=\"center\" px={3} height=\"fit\">\n      <Box px={2} py={0.5} borderRadius={4} fontSize={12} fontWeight={500} textTransform=\"capitalize\" {...STATUS_STYLES[status]}>\n        {status}\n      </Box>\n    </Flex>\n  );\n}\n\n// `fontVariantNumeric` has no prop of its own, so it goes through `css` — still one shared class, not\n// a style attribute.\nfunction AmountCell({ cell }: { cell: CellModel<Invoice> }) {\n  return (\n    <Flex ai=\"center\" jc=\"end\" px={3} height=\"fit\" css={{ fontVariantNumeric: 'tabular-nums' }}>\n      {money.format(cell.value as number)}\n    </Flex>\n  );\n}\n\nconst definition: GridDefinition<Invoice> = {\n  rowKey: 'id',\n  title: 'Invoices',\n  topBar: true,\n  bottomBar: true,\n  globalFilter: true,\n  rowSelection: { pinned: true },\n  rangeSelection: true,\n  footer: { label: 'All invoices' },\n  export: { fileName: 'invoices' },\n  visibleRowsCount: 12,\n  rowHeight: 44,\n  columns: [\n    { key: 'reference', header: 'Reference', width: 120, filterable: true },\n    { key: 'customer', header: 'Customer', width: 170, filterable: true },\n    { key: 'team', header: 'Team', width: 130, filterable: { type: 'multiselect' } },\n    { key: 'status', header: 'Status', width: 120, filterable: { type: 'multiselect' }, Cell: StatusCell, aggregate: 'count' },\n    {\n      key: 'amount',\n      header: 'Amount',\n      width: 130,\n      align: 'end',\n      editable: true,\n      filterable: { type: 'number' },\n      aggregate: 'sum',\n      Cell: AmountCell,\n      AggregateCell: ({ cell }) => (\n        <Flex ai=\"center\" jc=\"end\" px={3} height=\"fit\" fontWeight={600} css={{ fontVariantNumeric: 'tabular-nums' }}>\n          {cell.value === null ? '' : money.format(Number(cell.value))}\n        </Flex>\n      ),\n      // An export runs no React, so the column says what it writes rather than exporting the renderer.\n      exportValue: (row) => row.amount,\n      exportFormat: '#,##0',\n    },\n    { key: 'issued', header: 'Issued', width: 120 },\n  ],\n  // One function both judges an edit and is told about it: a string is a refusal, and the message is\n  // what the cell shows.\n  onCellEdit: ({ value }) => {\n    const amount = Number(value);\n\n    if (!Number.isFinite(amount) || amount <= 0) return 'An invoice is worth more than nothing.';\n    if (amount > 100_000) return 'Anything over $100,000 needs an approval.';\n  },\n};\n\nexport default function InvoicesGrid() {\n  return (\n    <Flex d=\"column\" gap={4}>\n      <Box>\n        <H2 fontSize={20} fontWeight={600}>\n          Invoices\n        </H2>\n        <P mt={1} fontSize={14} color=\"slate-600\" theme={{ dark: { color: 'slate-400' } }}>\n          Search the table, filter a column, group by Team from a column menu, edit an amount, select a block of cells and copy it, or\n          export what is on screen.\n        </P>\n      </Box>\n      <DataGrid data={invoices} def={definition} />\n    </Flex>\n  );\n}\n",
      "type": "registry:block",
      "target": "@components/box-kite/invoices-grid.tsx"
    },
    {
      "path": "registry/blocks/data-grid/invoice-rows.ts",
      "content": "/** The rows the block is wired to. Replace this file with your own fetch — the grid reads plain objects. */\nexport interface Invoice {\n  id: number;\n  reference: string;\n  customer: string;\n  team: string;\n  status: 'paid' | 'pending' | 'overdue';\n  amount: number;\n  issued: string;\n}\n\nconst TEAMS = ['Northern', 'Southern', 'Eastern', 'Western'];\nconst STATUSES = ['paid', 'pending', 'overdue'] as const;\nconst CUSTOMERS = [\n  'Ardent Supply',\n  'Beacon Freight',\n  'Corvus Analytics',\n  'Delta Hardware',\n  'Evergreen Mills',\n  'Fairline Media',\n  'Granite Foods',\n  'Harbour Logistics',\n];\n\n/**\n * Two hundred invoices generated from the index, so the block has something to sort, filter, group and\n * total without shipping a data file — and every reload shows the same table.\n */\nexport const invoices: Invoice[] = Array.from({ length: 200 }, (_, index) => ({\n  id: index + 1,\n  reference: `INV-${(2480 + index).toString()}`,\n  customer: CUSTOMERS[index % CUSTOMERS.length],\n  team: TEAMS[index % TEAMS.length],\n  status: STATUSES[index % STATUSES.length],\n  amount: 400 + ((index * 137) % 9600),\n  issued: new Date(Date.UTC(2026, index % 12, ((index * 7) % 27) + 1)).toISOString().slice(0, 10),\n}));\n",
      "type": "registry:lib",
      "target": "@components/box-kite/invoice-rows.ts"
    }
  ],
  "docs": "Render <InvoicesGrid /> anywhere. Replace `invoice-rows.ts` with your own fetch — the grid reads plain objects — and edit the `columns` array in `invoices-grid.tsx`."
}
