# Example: A custom section

toolPanel.sections states the complete rail: Columns, a consumer-authored Actions section, then Filters, with grouping left off. The custom pane's buttons reach the grid through the handle onGridReady delivers — scroll to a row, or export the grid as CSV.

Source: https://pretable.ai/examples/tool-panel-custom-section.md

```tsx CustomSectionGrid.tsx
"use client";

import { useMemo, useRef } from "react";

import { PretableSurface } from "@pretable/react";
import type {
  PretableColumn,
  PretableSurfaceGrid,
  PretableToolPanelConfig,
} from "@pretable/react";

import { columns } from "./columns";
import { trades, type Trade } from "./data";

const VIEWPORT_HEIGHT = 320;

function ActionsIcon({ className }: { className?: string }) {
  return (
    <svg
      aria-hidden="true"
      className={className}
      fill="none"
      height="16"
      stroke="currentColor"
      strokeWidth="1.5"
      viewBox="0 0 16 16"
      width="16"
    >
      <path d="M8 2v8M4.5 6.5 8 10l3.5-3.5" />
      <path d="M3 13h10" />
    </svg>
  );
}

export function CustomSectionGrid() {
  // The one route to the grid from inside a custom section: `onGridReady`
  // hands the surface's grid handle to a ref, and the section's `render`
  // closes over that ref. No context argument needed — or offered.
  const grid = useRef<PretableSurfaceGrid<
    Trade,
    string,
    readonly PretableColumn<Trade>[]
  > | null>(null);

  // The COMPLETE rail, in order: grouping is dropped, and the custom section
  // sits between the two built-ins it is interleaved with. Held stable in a
  // memo — a roster built inline would only rebuild the descriptor array each
  // render, but stable is the habit worth copying.
  const toolPanel = useMemo<PretableToolPanelConfig>(
    () => ({
      sections: [
        "columns",
        {
          id: "actions",
          icon: ActionsIcon,
          label: "Actions",
          render: () => (
            <div style={{ display: "grid", gap: 8, padding: 4 }}>
              <h3 style={{ fontSize: 13, margin: 0 }}>Actions</h3>
              <button
                onClick={() => grid.current?.scrollToRow("t1")}
                type="button"
              >
                Jump to first trade
              </button>
              <button
                onClick={() => grid.current?.scrollToRow("t28")}
                type="button"
              >
                Jump to last trade
              </button>
              <button onClick={() => grid.current?.exportCsv()} type="button">
                Download CSV
              </button>
            </div>
          ),
        },
        "filters",
      ],
      defaultActiveSection: "actions",
    }),
    [],
  );

  return (
    <div>
      <p style={{ margin: "0 0 8px", fontSize: 13 }}>
        The rail carries <strong>Columns</strong>, a custom{" "}
        <strong>Actions</strong> section, and <strong>Filters</strong> — in that
        order, with the grouping built-in left off the roster. The Actions pane
        is open on load via{" "}
        <code>defaultActiveSection: &quot;actions&quot;</code>; its buttons
        reach the grid through the handle <code>onGridReady</code> delivers —
        jump the viewport to either end, or download the grid as CSV.
      </p>
      <PretableSurface<Trade>
        ariaLabel="Trades"
        columns={columns}
        getRowId={(row) => row.id}
        onGridReady={(ready) => {
          grid.current = ready;
        }}
        rows={trades}
        toolPanel={toolPanel}
        viewportHeight={VIEWPORT_HEIGHT}
      />
    </div>
  );
}
```

```ts columns.ts
import type { PretableColumn } from "@pretable/react";

import type { Trade } from "./data";

const usd = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0,
});

export const columns: PretableColumn<Trade>[] = [
  { id: "symbol", header: "Symbol", widthPx: 90 },
  { id: "side", header: "Side", type: "enum", widthPx: 80 },
  { id: "desk", header: "Desk", type: "enum", widthPx: 100 },
  { id: "quantity", header: "Qty", type: "number", widthPx: 90 },
  {
    id: "price",
    header: "Price",
    type: "number",
    widthPx: 100,
    format: ({ value }) => usd.format(value as number),
  },
];
```

```ts data.ts
export interface Trade {
  id: string;
  symbol: string;
  side: string;
  desk: string;
  quantity: number;
  price: number;
}

const SYMBOLS = [
  ["NVDA", 122],
  ["MSFT", 416],
  ["AAPL", 213],
  ["LLY", 784],
  ["UNH", 528],
  ["XOM", 118],
  ["JPM", 260],
  ["GS", 538],
  ["CVX", 159],
  ["TLT", 89],
  ["USO", 70],
  ["QQQ", 503],
] as const;

const DESKS = ["Equities", "Credit", "Macro"] as const;

// Deterministic on purpose: a docs example that rendered differently on every
// load would make its own prose wrong.
export const trades: Trade[] = Array.from({ length: 28 }, (_, i) => {
  const [symbol, price] = SYMBOLS[i % SYMBOLS.length] as readonly [
    string,
    number,
  ];
  return {
    id: `t${i + 1}`,
    symbol,
    side: i % 3 === 0 ? "Sell" : "Buy",
    desk: DESKS[i % DESKS.length] as string,
    quantity: 50 + ((i * 37) % 400),
    price,
  };
});
```
