The Column Manager is a tree-based component provided by LyteNyte Grid PRO. It gives users intuitive control over column visibility, grouping, and order, while developers retain full flexibility over layout and styling.
The Column Manager renders a virtualized tree structure, meaning its implementation
typically uses a recursive RenderNode
function. This pattern makes it easy to handle nested column groups.
import { ColumnManager as CM } from "@1771technologies/lytenyte-pro";
type TreeItem = ReturnType<
typeof CM.useColumnManager<BankData>
>["items"][number];
export default function ColumnManager() {
const { items, lookup } = CM.useColumnManager({ grid });
return (
<CM.Root items={items} lookup={lookup}>
<CM.Panel
className="h-full w-full"
style={{ position: "relative", overflow: "auto" }}
>
{items.map((c) => {
return (
<RenderNode
item={c}
grid={grid}
key={c.kind === "branch" ? c.id : c.data.id}
/>
);
})}
{spacer}
</CM.Panel>
</CM.Root>
);
}
function RenderNode({ item, grid }: { item: TreeItem; grid: Grid<BankData> }) {
if (item.kind === "leaf") {
return (
<CM.Leaf item={item}>
<CM.MoveHandle>
<DragDotsSmallIcon />
</CM.MoveHandle>
<CM.VisibilityCheckbox />
<CM.Label />
</CM.Leaf>
);
}
const values = [...item.children.values()];
return (
<CM.Branch
item={item}
label={
<div style={{ display: "flex", gap: "2px" }}>
<CM.VisibilityCheckbox />
<CM.Label />
</div>
}
>
{values.map((c) => {
return (
<RenderNode
item={c}
grid={grid}
key={c.kind === "branch" ? c.id : c.data.id}
/>
);
})}
</CM.Branch>
);
}
The example above demonstrates a Column Manager where users can:
"use client";
import {
useClientRowDataSource,
Grid,
ColumnManager as CM,
} from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import {
ChevronDownIcon,
DragDotsSmallIcon,
} from "@1771technologies/lytenyte-pro/icons";
import type {
Column,
Grid as GridType,
} from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { ChevronRightIcon, Crosshair1Icon } from "@radix-ui/react-icons";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
type TreeItem = ReturnType<
typeof CM.useColumnManager<BankData>
>["items"][number];
const columns: Column<BankData>[] = [
{ id: "age", type: "number" },
{ id: "job" },
{ id: "balance", type: "number" },
{ id: "education" },
{ id: "marital", groupPath: ["Personal"] },
{ id: "default", groupPath: ["Personal"] },
{ id: "housing", groupPath: ["Personal"] },
{ id: "loan" },
{ id: "contact" },
{ id: "day", type: "number" },
{ id: "month" },
{ id: "duration" },
{ id: "campaign" },
{ id: "pdays" },
{ id: "previous" },
{ id: "poutcome" },
{ id: "y" },
];
export default function ColumnManager() {
const ds = useClientRowDataSource({
data: bankDataSmall,
transformInFilterItem: (params) => {
if (params.column.id === "age") {
return params.values.map((c) => {
const v = c as number;
let group;
if (v < 20) group = "0 < 20";
else if (v < 40) group = "20 < 40";
else if (v < 60) group = "40 < 60";
else group = "60+";
return {
id: `${v}`,
label: `${v} years`,
value: v,
groupPath: [group],
};
});
}
return params.values.map((c) => ({
id: `${c}`,
label: `${c}`,
value: c,
}));
},
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
});
const view = grid.view.useValue();
const { items, lookup } = CM.useColumnManager({
grid,
});
return (
<div>
<div className="bg-ln-gray-05 column-manager h-[300px]">
<CM.Root grid={grid} lookup={lookup}>
<CM.Panel
className="h-full w-full"
style={{ position: "relative", overflow: "auto" }}
>
{items.map((c) => {
return (
<RenderNode
item={c}
grid={grid}
key={c.kind === "branch" ? c.id : c.data.id}
/>
);
})}
</CM.Panel>
</CM.Root>
</div>
<div>
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") {
return (
<Grid.HeaderGroupCell
key={c.idOccurrence}
cell={c}
className="flex items-center gap-2 px-2"
>
<div>{c.groupPath.at(-1)}</div>
</Grid.HeaderGroupCell>
);
}
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
</div>
</div>
);
}
function RenderNode({
item,
grid,
}: {
item: TreeItem;
grid: GridType<BankData>;
}) {
if (item.kind === "leaf") {
return (
<CM.Leaf
item={item}
className="flex items-center gap-1 hover:bg-ln-gray-30 transition-all focus:bg-ln-primary-30 my-0 pl-6"
>
<CM.MoveHandle
className="flex items-center justify-center hover:bg-ln-gray-30 focus:ring-1"
placeholder={({ columns }) => {
const c = columns[0];
return (
<div className="px-2 py-1 bg-black text-white rounded flex gap-2 items-center">
<Crosshair1Icon />
{typeof c === "string" ? c : c.name ?? c.id}
</div>
);
}}
>
<DragDotsSmallIcon />
</CM.MoveHandle>
<CM.VisibilityCheckbox />
<CM.Label className="flex items-center flex-1 text-sm text-ln-gray-70 pl-1" />
</CM.Leaf>
);
}
const values = [...item.children.values()];
return (
<CM.Branch
item={item}
className="flex flex-col my-0"
labelWrapClassName="flex items-center gap-1 text-sm"
expander={(p) => {
return (
<button>
{p.expanded && <ChevronDownIcon />}
{!p.expanded && <ChevronRightIcon />}
</button>
);
}}
label={
<div style={{ display: "flex", gap: 6 }}>
<CM.MoveHandle
className="flex items-center justify-center hover:bg-ln-gray-30 focus:ring-1"
placeholder={({ columns }) => {
const c = columns[0];
return (
<div className="py-1 bg-black text-white rounded flex gap-2 items-center">
<Crosshair1Icon />
{typeof c === "string" ? c : c.name ?? c.id}
</div>
);
}}
>
<DragDotsSmallIcon />
</CM.MoveHandle>
<CM.VisibilityCheckbox />
<CM.Label />
</div>
}
>
{values.map((c) => {
return (
<RenderNode
item={c}
grid={grid}
key={c.kind === "branch" ? c.id : c.data.id}
/>
);
})}
</CM.Branch>
);
}
The Column Manager is composed of modular building blocks. Each part can be styled, extended, or overridden to match your application's design system:
Root
- Provides the root context for the Column Manager.
All other parts must be rendered inside this component.Panel
- The main container that renders the column tree.
Includes built-in keyboard navigation but otherwise behaves like a plain div
.Branch
- Represents a column group (i.e., a node in the tree with children).
Can contain both Leaf
items and nested Branch
items.Leaf
- Represents a single column in the grid. Supports drag-and-drop reordering and visibility toggling.MoveHandle
- A drag handle, usually rendered as an icon (e.g., ⋮⋮
),
that lets users reorder columns or column groups.VisibilityCheckbox
A checkbox that toggles whether a column
(or entire group) is visible in the grid.Label
Displays the column or column group's name.
Can be customized or replaced using the as
prop.