# Example: Resize, reorder, and pin

Column width, order, and pin are controlled here, so the layout gestures below the grid stay visible after each drag.

Source: https://pretable.ai/examples/column-layout.md

```tsx ColumnLayoutGrid.tsx
"use client";

import { useState } from "react";

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

import { columns } from "./columns";
import { instruments, type Instrument } from "./data";

const VIEWPORT_HEIGHT = 260;

function initialWidths(cols: PretableColumn<Instrument>[]) {
  const widths: Record<string, number> = {};
  for (const column of cols) {
    if (typeof column.widthPx === "number") {
      widths[column.id] = column.widthPx;
    }
  }
  return widths;
}

export function ColumnLayoutGrid() {
  // All three layout slices are controlled here, independently, so their
  // current values can be echoed below the grid — the same "controlled state
  // makes an invisible gesture legible" pattern as the grouping panel and
  // column filter examples.
  const [columnWidths, setColumnWidths] = useState<
    Partial<Record<string, number>>
  >(() => initialWidths(columns));
  const [columnOrder, setColumnOrder] = useState<readonly string[]>(() =>
    columns.map((column) => column.id),
  );
  const [columnPinned, setColumnPinned] = useState<
    Partial<Record<string, "left" | "right" | null>>
  >({ symbol: "left", note: "right" });

  return (
    <div>
      <p style={{ margin: "0 0 8px", fontSize: 13 }}>
        Drag a header to reorder, drag its right-edge handle to resize,
        double-click the handle to hand the width back to the grid.{" "}
        <strong>Symbol</strong> is pinned left and <strong>Note</strong> is
        pinned right — drag a column into either group to pin it there, or out
        to unpin it. Resizing needs a fine pointer: the handle is a 4px strip,
        so it is not drawn on a touch device.
      </p>
      <PretableSurface<Instrument>
        ariaLabel="Instrument positions"
        columns={columns}
        getRowId={(row) => row.id}
        onColumnOrderChange={setColumnOrder}
        onColumnPinnedChange={setColumnPinned}
        onColumnWidthsChange={setColumnWidths}
        rows={instruments}
        state={{ columnOrder, columnPinned, columnWidths }}
        viewportHeight={VIEWPORT_HEIGHT}
      />
      <p style={{ margin: "8px 0 0", fontSize: 13 }}>
        Order:{" "}
        <code>
          {columnOrder
            .map((id) => {
              const pin = columnPinned[id];
              return pin ? `${id} (${pin})` : id;
            })
            .join(" → ")}
        </code>
      </p>
      <p style={{ margin: "4px 0 0", fontSize: 13 }}>
        Widths:{" "}
        <code>
          {columnOrder
            .map((id) => `${id} ${columnWidths[id] ?? "—"}`)
            .join(" · ")}
        </code>
      </p>
    </div>
  );
}
```

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

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

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

// Plain `PretableColumn<Instrument>[]`, not `createColumnHelper` + `as const`:
// pin, order, and widths are all owned by this demo's own controlled state
// below rather than by the column declarations, so nothing here needs the
// helper's literal column-id tuple.
export const columns: PretableColumn<Instrument>[] = [
  { id: "symbol", header: "Symbol", widthPx: 90, minWidthPx: 60 },
  { id: "name", header: "Name", widthPx: 160 },
  { id: "sector", header: "Sector", widthPx: 120 },
  {
    id: "price",
    header: "Price",
    type: "number",
    widthPx: 90,
    format: ({ value }) => usd.format(value as number),
  },
  { id: "note", header: "Note", widthPx: 160, minWidthPx: 100 },
];
```

```ts data.ts
export interface Instrument {
  id: string;
  symbol: string;
  name: string;
  sector: string;
  price: number;
  note: string;
}

export const instruments: Instrument[] = [
  {
    id: "i1",
    symbol: "NVDA",
    name: "NVIDIA",
    sector: "Technology",
    price: 118.32,
    note: "Core position",
  },
  {
    id: "i2",
    symbol: "MSFT",
    name: "Microsoft",
    sector: "Technology",
    price: 421.9,
    note: "Trimming",
  },
  {
    id: "i3",
    symbol: "LLY",
    name: "Eli Lilly",
    sector: "Healthcare",
    price: 812.55,
    note: "Watching earnings",
  },
  {
    id: "i4",
    symbol: "JPM",
    name: "JPMorgan Chase",
    sector: "Financials",
    price: 214.07,
    note: "Core position",
  },
  {
    id: "i5",
    symbol: "XOM",
    name: "Exxon Mobil",
    sector: "Energy",
    price: 117.44,
    note: "Hedge",
  },
  {
    id: "i6",
    symbol: "UNH",
    name: "UnitedHealth",
    sector: "Healthcare",
    price: 498.2,
    note: "New entry",
  },
];
```
