Column Field
A column's field determines how LyteNyte Grid retrieves a cell value. This guide explains the four field types supported by LyteNyte Grid.
Set a column’s value source using the field property on the column definition.
The field property applies only to individual columns and cannot be set on the
base column specification.
LyteNyte Grid supports four field types:
- String: Use when each row is a flat JavaScript object. The string value is used as an object key to read the cell value.
- Number: Use when each row is an array. The number value is used as an array index to read the cell value.
- Function: Use to compute the cell value from the row data. This callback can run any logic and is commonly used for derived values.
- Path object: Use when each row is a nested JavaScript object. Provide an object of the form
{ kind: "path"; path: string }to read a nested cell value.
The field property is optional. If omitted, LyteNyte Grid uses the column’s id as
a fallback, which behaves the same as a string field.
String Fields
When field is a string, the grid uses it as a key to read the value from
object-based row data. Use string fields when each row is a standard JavaScript object.
This is demonstrated in the example below:
String Field
13 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import "@1771technologies/lytenyte-pro/pill-manager.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";14
15export interface GridSpec {16 readonly data: DEXPerformanceData;17}18
19const initialColumns: Grid.Column<GridSpec>[] = [20 { id: "symbol", field: "symbol", cellRenderer: SymbolCell, width: 250, name: "Symbol" },21 { id: "network", field: "network", cellRenderer: NetworkCell, width: 220, name: "Network" },22 { id: "exchange", field: "exchange", cellRenderer: ExchangeCell, width: 220, name: "Exchange" },23
58 collapsed lines
24 {25 id: "change24h",26 field: "change24h",27 cellRenderer: PercentCellPositiveNegative,28 headerRenderer: makePerfHeaderCell("Change", "24h"),29 name: "Change % 24h",30 type: "number,",31 },32
33 {34 id: "perf1w",35 field: "perf1w",36 cellRenderer: PercentCellPositiveNegative,37 headerRenderer: makePerfHeaderCell("Perf %", "1w"),38 name: "Perf % 1W",39 type: "number,",40 },41 {42 id: "perf1m",43 field: "perf1m",44 cellRenderer: PercentCellPositiveNegative,45 headerRenderer: makePerfHeaderCell("Perf %", "1m"),46 name: "Perf % 1M",47 type: "number,",48 },49 {50 id: "perf3m",51 field: "perf3m",52 cellRenderer: PercentCellPositiveNegative,53 headerRenderer: makePerfHeaderCell("Perf %", "3m"),54 name: "Perf % 3M",55 type: "number,",56 },57 {58 id: "perf6m",59 field: "perf6m",60 cellRenderer: PercentCellPositiveNegative,61 headerRenderer: makePerfHeaderCell("Perf %", "6m"),62 name: "Perf % 6M",63 type: "number,",64 },65 {66 id: "perfYtd",67 field: "perfYtd",68 cellRenderer: PercentCellPositiveNegative,69 headerRenderer: makePerfHeaderCell("Perf %", "YTD"),70 name: "Perf % YTD",71 type: "number",72 },73 { id: "volatility", field: "volatility", cellRenderer: PercentCell, name: "Volatility", type: "number" },74 {75 id: "volatility1m",76 field: "volatility1m",77 cellRenderer: PercentCell,78 headerRenderer: makePerfHeaderCell("Volatility", "1m"),79 name: "Volatility 1M",80 type: "number",81 },82];83
84const base: Grid.ColumnBase<GridSpec> = { width: 80 };85
86export default function ColumnDemo() {87 const ds = useClientDataSource({ data: data });88
89 return (90 <>91 <div92 className="ln-grid ln-cell:text-xs ln-header:text-xs ln-header:text-ln-text-xlight"93 style={{ height: 500 }}94 >95 <Grid columns={initialColumns} columnBase={base} rowSource={ds} />96 </div>97 </>98 );99}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};In the example, each column specifies its field directly:
1const columns = [2 { id: "Symbol", field: "symbol" },3 { id: "Network", field: "network" },4 // other columns5];Number Fields
When field is a number, row data must be an array. The number acts as the index used
to retrieve the value. In the demo below, each row is an array, so each column uses a numeric index.
Number Index Fields
10 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import { Grid, useClientDataSource } from "@1771technologies/lytenyte-pro";3import { stockData } from "@1771technologies/grid-sample-data/stock-data-smaller";4import { AnalystRatingCell, PercentCell, CurrencyCell, SymbolCell, CompactNumberCell } from "./components.js";5
6type StockData = (typeof stockData)[number];7
8export interface GridSpec {9 readonly data: StockData;10}11
12const columns: Grid.Column<GridSpec>[] = [13 { field: 0, id: "symbol", name: "Symbol", cellRenderer: SymbolCell, width: 220 },14 { field: 2, id: "analyst-rating", name: "Analyst Rating", cellRenderer: AnalystRatingCell, width: 130 },15 { field: 3, id: "price", type: "number", name: "Price", cellRenderer: CurrencyCell, width: 110 },16 { field: 5, id: "change", type: "number", name: "Change", cellRenderer: PercentCell, width: 130 },17 { field: 11, id: "eps", name: "EPS", type: "number", cellRenderer: CurrencyCell, width: 130 },18 { field: 6, id: "volume", name: "Volume", type: "number", cellRenderer: CompactNumberCell, width: 130 },19];20
21const base: Grid.ColumnBase<GridSpec> = { widthFlex: 1 };22
23export default function ColumnDemo() {24 const ds = useClientDataSource({ data: stockData });25
26 return (27 <div className="ln-grid ln-cell:text-xs ln-cell:font-light ln-header:text-xs" style={{ height: 500 }}>28 <Grid columns={columns} columnBase={base} rowSource={ds} />29 </div>30 );31}1import type { Grid } from "@1771technologies/lytenyte-pro";2import { memo, type ReactNode } from "react";3import { logos } from "@1771technologies/grid-sample-data/stock-data-smaller";4import type { ClassValue } from "clsx";5import clsx from "clsx";6import { twMerge } from "tailwind-merge";7import type { GridSpec } from "./demo";8
9export function tw(...c: ClassValue[]) {10 return twMerge(clsx(...c));11}12
13function AnalystRatingCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {14 const field = api.columnField(column, row) as string;15
16 let Icon: (() => ReactNode) | null = null;17 const label = field || "-";18 let clx = "";19 if (label === "Strong buy") {20 Icon = StrongBuy;21 clx = "text-green-500";22 } else if (label === "Strong Sell") {23 Icon = StrongSell;24 clx = "text-red-500";25 } else if (label === "Neutral") {26 Icon = Minus;27 } else if (label === "Buy") {28 Icon = Buy;29 clx = "text-green-500";30 } else if (label === "Sell") {31 Icon = Sell;32 clx = "text-red-500";33 }34
35 return (36 <div className={tw("grid h-full grid-cols-[16px_1fr] items-center gap-4 text-nowrap", clx)}>37 {Icon && <Icon />}38 <div>{label}</div>39 </div>40 );41}42
43function Minus() {44 return (45 <svg46 xmlns="http://www.w3.org/2000/svg"47 fill="none"48 viewBox="0 0 24 24"49 strokeWidth={1.5}50 stroke="currentColor"51 width={16}52 >53 <path strokeLinecap="round" strokeLinejoin="round" d="M5 12h14" />54 </svg>55 );56}57
58function Sell() {59 return (60 <svg61 xmlns="http://www.w3.org/2000/svg"62 fill="none"63 viewBox="0 0 24 24"64 strokeWidth={1.5}65 stroke="currentColor"66 width={16}67 >68 <path strokeLinecap="round" strokeLinejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />69 </svg>70 );71}72
73function Buy() {74 return (75 <svg76 xmlns="http://www.w3.org/2000/svg"77 fill="none"78 viewBox="0 0 24 24"79 strokeWidth={1.5}80 stroke="currentColor"81 width={16}82 >83 <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />84 </svg>85 );86}87
88function StrongSell() {89 return (90 <svg91 xmlns="http://www.w3.org/2000/svg"92 fill="none"93 viewBox="0 0 24 24"94 strokeWidth={1.5}95 stroke="currentColor"96 width={16}97 >98 <path99 strokeLinecap="round"100 strokeLinejoin="round"101 d="m4.5 5.25 7.5 7.5 7.5-7.5m-15 6 7.5 7.5 7.5-7.5"102 />103 </svg>104 );105}106
107function StrongBuy() {108 return (109 <svg110 xmlns="http://www.w3.org/2000/svg"111 fill="none"112 viewBox="0 0 24 24"113 strokeWidth={1.5}114 stroke="currentColor"115 width={16}116 >117 <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 18.75 7.5-7.5 7.5 7.5" />118 <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 7.5-7.5 7.5 7.5" />119 </svg>120 );121}122
123export const AnalystRatingCell = memo(AnalystRatingCellImpl);124
125function CompactNumberCellImpl({ row, api, column }: Grid.T.CellRendererParams<GridSpec>) {126 const field = api.columnField(column, row);127 const [label, suffix] = typeof field === "number" ? formatCompactNumber(field) : ["-", ""];128
129 return (130 <div className="flex h-full w-full items-center justify-end gap-1 text-nowrap tabular-nums">131 <span>{label}</span>132 <span className="font-semibold">{suffix}</span>133 </div>134 );135}136
137export const CompactNumberCell = memo(CompactNumberCellImpl);138function formatCompactNumber(n: number) {139 const suffixes = ["", "K", "M", "B", "T"];140 let magnitude = 0;141 let num = Math.abs(n);142
143 while (num >= 1000 && magnitude < suffixes.length - 1) {144 num /= 1000;145 magnitude++;146 }147
148 const decimals = 2;149 const formatted = num.toFixed(decimals);150
151 return [`${n < 0 ? "-" : ""}${formatted}`, suffixes[magnitude]];152}153
154const formatter = new Intl.NumberFormat("en-US", {155 minimumFractionDigits: 2,156 maximumFractionDigits: 2,157});158
159function CurrencyCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {160 const field = api.columnField(column, row);161
162 const label = typeof field === "number" ? formatter.format(field) : "-";163
164 return (165 <div className="flex h-full w-full items-center justify-end text-nowrap tabular-nums">166 <div className="flex items-baseline gap-1">167 <span>{label}</span>168 <span className="text-[9px]">USD</span>169 </div>170 </div>171 );172}173
174export const CurrencyCell = memo(CurrencyCellImpl);175
176function PercentCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {177 const field = api.columnField(column, row) as number;178
179 const label = typeof field === "number" ? formatter.format(field) + "%" : "-";180
181 return (182 <div className={tw("flex h-full w-full items-center justify-end text-nowrap tabular-nums")}>{label}</div>183 );184}185
186export const PercentCell = memo(PercentCellImpl);187
188function SymbolCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {189 if (!api.rowIsLeaf(row)) return null;190
191 const symbol = api.columnField(column, row) as string;192 const desc = row.data?.[1];193
194 return (195 <div className="grid h-full w-full grid-cols-[32px_1fr] items-center gap-3 overflow-hidden text-nowrap">196 <div className="flex h-8 min-h-8 w-8 min-w-8 items-center justify-center overflow-hidden rounded-full">197 <img198 src={logos[symbol]}199 alt=""200 width={26}201 height={26}202 className="h-6.5 min-h-6.5 w-6.5 pointer-events-none min-w-[26] rounded-full bg-black p-1"203 />204 </div>205 <div className="overflow-hidden text-ellipsis">{desc}</div>206 </div>207 );208}209export const SymbolCell = memo(SymbolCellImpl);The column definitions specify numeric indices. These indices do not need to be sequential:
1const columns: Grid.Column<GridSpec>[] = [2 { field: 0, id: "symbol", name: "Symbol" },3 { field: 2, id: "analyst-rating", name: "Analyst Rating" },4 { field: 3, id: "price", type: "number", name: "Price" },5 { field: 5, id: "change", type: "number", name: "Change" },6 { field: 11, id: "eps", name: "EPS", type: "number" },7 { field: 6, id: "volume", name: "Volume", type: "number" },8];Number fields are efficient. In JavaScript, array indexing usually performs better than key-based lookups, and array-structured row data is often more compact than object-based data. This makes number fields a strong choice for performance-sensitive grids.
Function Fields
A function field offers the most flexible way to compute a column’s value. You provide a function, and LyteNyte Grid calls it for each row to compute the cell value. Function fields are commonly mixed with other field types rather than being used for every column.
LyteNyte Grid passes a single argument to the function. This argument contains either aggregated group data or leaf-row data, depending on the row being processed. See the field data param for the exact type.
Function fields are ideal for derived or calculated values. In the demo below, the GBP Price column computes a value from the base USD Price column.
Function Fields
17 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import { Grid, useClientDataSource } from "@1771technologies/lytenyte-pro";3import { stockData } from "@1771technologies/grid-sample-data/stock-data-smaller";4import {5 AnalystRatingCell,6 PercentCell,7 CurrencyCell,8 SymbolCell,9 CompactNumberCell,10 CurrencyCellGBP,11} from "./components.jsx";12
13type StockData = (typeof stockData)[number];14
15export interface GridSpec {16 readonly data: StockData;17}18
19const columns: Grid.Column<GridSpec>[] = [20 { field: 0, id: "symbol", name: "Symbol", cellRenderer: SymbolCell, width: 220 },21 { field: 2, id: "analyst-rating", name: "Analyst Rating", cellRenderer: AnalystRatingCell, width: 130 },22 {23 field: 3,24 id: "price",25 name: "USD Price",26 type: "number",27 cellRenderer: CurrencyCell,28 width: 110,29 },30 {31 field: ({ row }) => {32 if (row.kind === "branch" || !row.data) return 0;33 return ((row.data as StockData)[3] as number) * 1.36;34 },35 id: "price gbp",36 name: "GBP Price",37 type: "number",38 cellRenderer: CurrencyCellGBP,39 width: 110,40 },41 { field: 5, id: "change", name: "Change", type: "number", cellRenderer: PercentCell, width: 130 },42 { field: 11, id: "eps", name: "EPS", type: "number", cellRenderer: CurrencyCell, width: 130 },43 { field: 6, id: "volume", name: "Volume", type: "number", cellRenderer: CompactNumberCell, width: 130 },44];45
46const base: Grid.ColumnBase<GridSpec> = { widthFlex: 1 };47
48export default function ColumnDemo() {49 const ds = useClientDataSource({ data: stockData });50
51 return (52 <div className="ln-grid ln-cell:text-xs ln-cell:font-light ln-header:text-xs" style={{ height: 500 }}>53 <Grid columns={columns} columnBase={base} rowSource={ds} />54 </div>55 );56}1import type { Grid } from "@1771technologies/lytenyte-pro";2import { memo, type ReactNode } from "react";3import { logos } from "@1771technologies/grid-sample-data/stock-data-smaller";4import type { ClassValue } from "clsx";5import clsx from "clsx";6import { twMerge } from "tailwind-merge";7import type { GridSpec } from "./demo";8
9export function tw(...c: ClassValue[]) {10 return twMerge(clsx(...c));11}12
13function AnalystRatingCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {14 const field = api.columnField(column, row) as string;15
16 let Icon: (() => ReactNode) | null = null;17 const label = field || "-";18 let clx = "";19 if (label === "Strong buy") {20 Icon = StrongBuy;21 clx = "text-green-500";22 } else if (label === "Strong Sell") {23 Icon = StrongSell;24 clx = "text-red-500";25 } else if (label === "Neutral") {26 Icon = Minus;27 } else if (label === "Buy") {28 Icon = Buy;29 clx = "text-green-500";30 } else if (label === "Sell") {31 Icon = Sell;32 clx = "text-red-500";33 }34
35 return (36 <div className={tw("grid h-full grid-cols-[16px_1fr] items-center gap-4 text-nowrap", clx)}>37 {Icon && <Icon />}38 <div>{label}</div>39 </div>40 );41}42
43function Minus() {44 return (45 <svg46 xmlns="http://www.w3.org/2000/svg"47 fill="none"48 viewBox="0 0 24 24"49 strokeWidth={1.5}50 stroke="currentColor"51 width={16}52 >53 <path strokeLinecap="round" strokeLinejoin="round" d="M5 12h14" />54 </svg>55 );56}57
58function Sell() {59 return (60 <svg61 xmlns="http://www.w3.org/2000/svg"62 fill="none"63 viewBox="0 0 24 24"64 strokeWidth={1.5}65 stroke="currentColor"66 width={16}67 >68 <path strokeLinecap="round" strokeLinejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />69 </svg>70 );71}72
73function Buy() {74 return (75 <svg76 xmlns="http://www.w3.org/2000/svg"77 fill="none"78 viewBox="0 0 24 24"79 strokeWidth={1.5}80 stroke="currentColor"81 width={16}82 >83 <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />84 </svg>85 );86}87
88function StrongSell() {89 return (90 <svg91 xmlns="http://www.w3.org/2000/svg"92 fill="none"93 viewBox="0 0 24 24"94 strokeWidth={1.5}95 stroke="currentColor"96 width={16}97 >98 <path99 strokeLinecap="round"100 strokeLinejoin="round"101 d="m4.5 5.25 7.5 7.5 7.5-7.5m-15 6 7.5 7.5 7.5-7.5"102 />103 </svg>104 );105}106
107function StrongBuy() {108 return (109 <svg110 xmlns="http://www.w3.org/2000/svg"111 fill="none"112 viewBox="0 0 24 24"113 strokeWidth={1.5}114 stroke="currentColor"115 width={16}116 >117 <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 18.75 7.5-7.5 7.5 7.5" />118 <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 7.5-7.5 7.5 7.5" />119 </svg>120 );121}122
123export const AnalystRatingCell = memo(AnalystRatingCellImpl);124
125function CompactNumberCellImpl({ row, api, column }: Grid.T.CellRendererParams<GridSpec>) {126 const field = api.columnField(column, row);127 const [label, suffix] = typeof field === "number" ? formatCompactNumber(field) : ["-", ""];128
129 return (130 <div className="flex h-full w-full items-center justify-end gap-1 text-nowrap tabular-nums">131 <span>{label}</span>132 <span className="font-semibold">{suffix}</span>133 </div>134 );135}136
137export const CompactNumberCell = memo(CompactNumberCellImpl);138function formatCompactNumber(n: number) {139 const suffixes = ["", "K", "M", "B", "T"];140 let magnitude = 0;141 let num = Math.abs(n);142
143 while (num >= 1000 && magnitude < suffixes.length - 1) {144 num /= 1000;145 magnitude++;146 }147
148 const decimals = 2;149 const formatted = num.toFixed(decimals);150
151 return [`${n < 0 ? "-" : ""}${formatted}`, suffixes[magnitude]];152}153
154const formatter = new Intl.NumberFormat("en-US", {155 minimumFractionDigits: 2,156 maximumFractionDigits: 2,157});158
159function CurrencyCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {160 const field = api.columnField(column, row);161
162 const label = typeof field === "number" ? formatter.format(field) : "-";163
164 return (165 <div className="flex h-full w-full items-center justify-end text-nowrap tabular-nums">166 <div className="flex items-baseline gap-1">167 <span>{label}</span>168 <span className="text-[9px]">USD</span>169 </div>170 </div>171 );172}173
174export const CurrencyCell = memo(CurrencyCellImpl);175
176function CurrencyCellGBPImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {177 const field = api.columnField(column, row);178
179 const label = typeof field === "number" ? formatter.format(field) : "-";180
181 return (182 <div className="flex h-full w-full items-center justify-end text-nowrap tabular-nums">183 <div className="flex items-baseline gap-1">184 <span>{label}</span>185 <span className="text-[9px]">GBP</span>186 </div>187 </div>188 );189}190
191export const CurrencyCellGBP = memo(CurrencyCellGBPImpl);192
193function PercentCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {194 const field = api.columnField(column, row) as number;195
196 const label = typeof field === "number" ? formatter.format(field) + "%" : "-";197
198 return (199 <div className={tw("flex h-full w-full items-center justify-end text-nowrap tabular-nums")}>{label}</div>200 );201}202
203export const PercentCell = memo(PercentCellImpl);204
205function SymbolCellImpl({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {206 if (!api.rowIsLeaf(row)) return null;207
208 const symbol = api.columnField(column, row) as string;209 const desc = row.data?.[1];210
211 return (212 <div className="grid h-full w-full grid-cols-[32px_1fr] items-center gap-3 overflow-hidden text-nowrap">213 <div className="flex h-8 min-h-8 w-8 min-w-8 items-center justify-center overflow-hidden rounded-full">214 <img215 src={logos[symbol]}216 alt=""217 width={26}218 height={26}219 className="h-6.5 min-h-6.5 w-6.5 pointer-events-none min-w-[26] rounded-full bg-black p-1"220 />221 </div>222 <div className="overflow-hidden text-ellipsis">{desc}</div>223 </div>224 );225}226export const SymbolCell = memo(SymbolCellImpl);Path Fields
Set the field property to an object of the form { kind: "path"; path: string } to retrieve a nested value from the row data.
The path syntax matches Lodash’s get function. Examples:
1{ kind: "path", path: "alpha.beta[0]" };2{ kind: "path", path: "beta[0].alpha" };The demo below shows path fields in use. Each row exposes temperatures
under a temps property keyed by month, so fields such
as { kind: "path", path: "temps.Jan" } read the appropriate value.
Path Fields
4 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import { Grid, useClientDataSource } from "@1771technologies/lytenyte-pro";3import type { MonthlyTemperature } from "@1771technologies/grid-sample-data/temperatures";4import { data } from "@1771technologies/grid-sample-data/temperatures";5
6export interface GridSpec {7 readonly data: MonthlyTemperature;8}9
10const columns: Grid.Column<GridSpec>[] = [11 { id: "year", name: "Year", cellRenderer: YearCell, width: 100 },12 { id: "Jan", field: { kind: "path", path: "temps.Jan" } },13 { id: "Feb", field: { kind: "path", path: "temps.Feb" } },14 { id: "Mar", field: { kind: "path", path: "temps.Mar" } },15 { id: "Apr", field: { kind: "path", path: "temps.Apr" } },16 { id: "May", field: { kind: "path", path: "temps.May" } },17 { id: "Jun", field: { kind: "path", path: "temps.Jun" } },18 { id: "Jul", field: { kind: "path", path: "temps.Jul" } },19 { id: "Aug", field: { kind: "path", path: "temps.Aug" } },20 { id: "Sep", field: { kind: "path", path: "temps.Sep" } },21 { id: "Oct", field: { kind: "path", path: "temps.Oct" } },22 { id: "Nov", field: { kind: "path", path: "temps.Nov" } },23 { id: "Dec", field: { kind: "path", path: "temps.Dec" } },24];25
26const base: Grid.ColumnBase<GridSpec> = { widthMin: 30, width: 50, widthFlex: 1, cellRenderer: HeatMapCell };27
28export default function ColumnDemo() {29 const ds = useClientDataSource({ data: data });30
31 return (32 <div33 className="ln-grid ln-cell:tabular-nums ln-header:tabular-nums ln-cell:px-0 ln-row-hover:bg-ln-primary-50/10 ln-cell:border-e ln-cell:border-ln-border-strong ln-header:justify-center"34 style={{ height: 500 }}35 >36 <Grid rowSource={ds} columns={columns} columnBase={base} />37 </div>38 );39}40
41export function HeatMapCell({ api, column, row }: Grid.T.CellRendererParams<GridSpec>) {42 if (!api.rowIsLeaf(row) || !row.data) return null;43
44 const value = row.data.temps[column.id as keyof MonthlyTemperature["temps"]];45
46 const bg = valueToColor(value);47
48 return (49 <div50 style={{ background: bg }}51 className="flex h-full w-full items-center justify-center text-white group-hover:opacity-70"52 >53 {value.toFixed(1)}°C54 </div>55 );56}57
58export function YearCell({ api, row, column }: Grid.T.CellRendererParams<GridSpec>) {59 const field = api.columnField(column, row);60
61 return <div className="flex h-full w-full items-center justify-center">{String(field)}</div>;62}63
64/**65 * Interpolate between two HSL colors based on value in [0, 30].66 * 0 -> hsl(173.4 80.4% 40%)67 * 30 -> hsl(0 84.2% 60.2%)68 */69export function valueToColor(value: number): string {70 const percentage = Math.min((value / 19) * 100, 100);71
72 const start = "174.7 83.9% 31.6%";73 const end = "178.6 84.3% 10%";74
75 return `color-mix(in hsl, hsl(${start}) ${100 - percentage}%, hsl(${end}) ${percentage}%)`;76}77//#endNext Steps
- Column Resizing: Change column widths programmatically or via user interaction.
- Column ID & Name: Define user-friendly column names and ensure unique IDs.
- Column Moving: Reorder columns programmatically or through drag-and-drop.
- Column Base: Learn how to specify default configuration options for columns.
