Column Manager
Use an opinionated Column Manager component to handle column visibility and ordering.
Use the ColumnManager component to provide a UI for managing grid columns.
ColumnManager handles the common boilerplate for showing, hiding,
and reordering columns.
Column Manager Implementation
The demo below shows the Column Manager. Click a checkbox to toggle a column’s visibility, or drag a column within the manager to reorder it.
Column Manager
100 collapsed lines
1import "@1771technologies/lytenyte-pro/components.css";2import "@1771technologies/lytenyte-pro/light-dark.css";3import { Grid, useClientDataSource } from "@1771technologies/lytenyte-pro";4import {5 ExchangeCell,6 makePerfHeaderCell,7 NetworkCell,8 PercentCell,9 PercentCellPositiveNegative,10 SymbolCell,11} from "./components.jsx";12import type { DEXPerformanceData } from "@1771technologies/grid-sample-data/dex-pairs-performance";13import { data } from "@1771technologies/grid-sample-data/dex-pairs-performance";14import { useState } from "react";15import { ColumnManager } from "@1771technologies/lytenyte-pro/components";16
17export interface GridSpec {18 readonly data: DEXPerformanceData;19}20
21const initialColumns: Grid.Column<GridSpec>[] = [22 { id: "symbol", groupPath: ["Market Info"], cellRenderer: SymbolCell, width: 250, name: "Symbol" },23 {24 id: "network",25 groupPath: ["Market Info"],26 cellRenderer: NetworkCell,27 width: 220,28 hide: true,29 name: "Network",30 },31 {32 id: "exchange",33 groupPath: ["Market Info"],34 cellRenderer: ExchangeCell,35 width: 220,36 hide: true,37 name: "Exchange",38 },39
40 {41 id: "change24h",42 groupPath: ["Performance"],43 cellRenderer: PercentCellPositiveNegative,44 headerRenderer: makePerfHeaderCell("Change", "24h"),45 name: "Change % 24h",46 type: "number,",47 },48
49 {50 id: "perf1w",51 groupPath: ["Performance"],52 cellRenderer: PercentCellPositiveNegative,53 headerRenderer: makePerfHeaderCell("Perf %", "1w"),54 name: "Perf % 1W",55 type: "number,",56 },57 {58 id: "perf1m",59 groupPath: ["Performance"],60 cellRenderer: PercentCellPositiveNegative,61 headerRenderer: makePerfHeaderCell("Perf %", "1m"),62 name: "Perf % 1M",63 type: "number,",64 },65 {66 id: "perf3m",67 groupPath: ["Performance"],68 cellRenderer: PercentCellPositiveNegative,69 headerRenderer: makePerfHeaderCell("Perf %", "3m"),70 name: "Perf % 3M",71 type: "number,",72 },73 {74 id: "perf6m",75 groupPath: ["Performance"],76 cellRenderer: PercentCellPositiveNegative,77 headerRenderer: makePerfHeaderCell("Perf %", "6m"),78 name: "Perf % 6M",79 type: "number,",80 },81 {82 id: "perfYtd",83 groupPath: ["Performance"],84 cellRenderer: PercentCellPositiveNegative,85 headerRenderer: makePerfHeaderCell("Perf %", "YTD"),86 name: "Perf % YTD",87 type: "number",88 },89 { id: "volatility", cellRenderer: PercentCell, name: "Volatility", type: "number" },90 {91 id: "volatility1m",92 cellRenderer: PercentCell,93 headerRenderer: makePerfHeaderCell("Volatility", "1m"),94 name: "Volatility 1M",95 type: "number",96 },97];98
99const base: Grid.ColumnBase<GridSpec> = { width: 80 };100
101export default function ComponentDemo() {102 const ds = useClientDataSource({ data: data });103 const [columns, setColumns] = useState(initialColumns);104
105 return (106 <div className="ln-grid flex">107 <div className="bg-ln-bg-ui-panel min-w-60 py-2">108 <div className="h-full w-full">109 <ColumnManager columns={columns} onColumnsChange={setColumns} />110 </div>111 </div>112 <div113 className="ln-grid ln-cell:text-xs ln-header:text-xs ln-header-group:justify-center ln-header:text-ln-text-xlight border-ln-border flex-1 border-s"114 style={{ height: 500 }}115 >116 <Grid columns={columns} columnBase={base} rowSource={ds} />117 </div>118 </div>119 );120}1import type { ClassValue } from "clsx";2import clsx from "clsx";3import { twMerge } from "tailwind-merge";4import { exchanges, networks, symbols } from "@1771technologies/grid-sample-data/dex-pairs-performance";5
6export function tw(...c: ClassValue[]) {7 return twMerge(clsx(...c));8}9import type { Grid } from "@1771technologies/lytenyte-pro";10import type { GridSpec } from "./demo";11
12export function SymbolCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {13 if (!api.rowIsLeaf(row) || !row.data) return null;14
15 const ticker = row.data.symbolTicker;16 const symbol = row.data.symbol;17 const image = symbols[row.data.symbolTicker];18
19 return (20 <div className="grid grid-cols-[20px_auto_auto] items-center gap-1.5">21 <div>22 <img23 src={image}24 alt={`Logo for symbol ${symbol}`}25 className="h-full w-full overflow-hidden rounded-full"26 />27 </div>28 <div className="bg-ln-gray-20 text-ln-text-dark flex h-fit items-center justify-center rounded-lg px-2 py-px text-[10px]">29 {ticker}30 </div>31 <div className="w-full overflow-hidden text-ellipsis">{symbol.split("/")[0]}</div>32 </div>33 );34}35
36export function NetworkCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {37 if (!api.rowIsLeaf(row) || !row.data) return null;38
39 const name = row.data.network;40 const image = networks[name];41
42 return (43 <div className="grid grid-cols-[20px_1fr] items-center gap-1.5">44 <div>45 <img46 src={image}47 alt={`Logo for network ${name}`}48 className="h-full w-full overflow-hidden rounded-full"49 />50 </div>51 <div className="w-full overflow-hidden text-ellipsis">{name}</div>52 </div>53 );54}55
56export function ExchangeCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {57 if (!api.rowIsLeaf(row) || !row.data) return null;58
59 const name = row.data.exchange;60 const image = exchanges[name];61
62 return (63 <div className="grid grid-cols-[20px_1fr] items-center gap-1.5">64 <div>65 <img66 src={image}67 alt={`Logo for exchange ${name}`}68 className="h-full w-full overflow-hidden rounded-full"69 />70 </div>71 <div className="w-full overflow-hidden text-ellipsis">{name}</div>72 </div>73 );74}75
76export function PercentCellPositiveNegative({ api, column, row }: Grid.T.CellRendererParams<GridSpec>) {77 if (!api.rowIsLeaf(row) || !row.data) return null;78
79 const field = api.columnField(column, row);80
81 if (typeof field !== "number") return "-";82
83 const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";84
85 return (86 <div87 className={tw(88 "h-ful flex w-full items-center justify-end tabular-nums",89 field < 0 ? "text-red-600 dark:text-red-300" : "text-green-600 dark:text-green-300",90 )}91 >92 {value}93 </div>94 );95}96
97export function PercentCell({ api, column, row }: Grid.T.CellRendererParams<GridSpec>) {98 if (!api.rowIsLeaf(row) || !row.data) return null;99
100 const field = api.columnField(column, row);101
102 if (typeof field !== "number") return "-";103
104 const value = (field > 0 ? "+" : "") + (field * 100).toFixed(2) + "%";105
106 return <div className="h-ful flex w-full items-center justify-end tabular-nums">{value}</div>;107}108
109export const makePerfHeaderCell = (name: string, subname: string) => {110 return (_: Grid.T.HeaderParams<GridSpec>) => {111 return (112 <div className="flex h-full w-full flex-col items-end justify-center tabular-nums">113 <div>{name}</div>114 <div className="text-ln-text-light font-mono uppercase">{subname}</div>115 </div>116 );117 };118};As shown in the demo code, configuring the Column Manager is straightforward. It abstracts the complexity of column state management into a single, easy-to-use component.
1<ColumnManager columns={columns} onColumnsChange={setColumns} />Note
For a less opinionated approach, use the Pill Manager or Tree View. Both are headless and allow flexible configuration.
Properties
Prop
The columns that are managed by the Column Manager.
An event callback that fires when the columns change.
The base column definition, when a base column is used.
Additional content rendered after the column row content. This is typically used to add a column menu.
Next Steps
- Pill Manager: Create and manage grid interactions using label pills.
- Tree View: Render and interact with hierarchical data using the Tree View component.
- Menu Button: Display a list of actions or options in an accessible dropdown menu.
