Cell Edit Renderers
To enable cell editing, provide an edit renderer for each editable column. This guide covers edit renderers for basic data types.
Text Editors
A text edit renderer uses a <input /> element. In the demo below,
double-click a cell in the Customer column to begin text editing.
Text Edit Renderer
20 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import type { OrderData } from "@1771technologies/grid-sample-data/orders";3import { data as initialData } from "@1771technologies/grid-sample-data/orders";4import {5 AvatarCell,6 EmailCell,7 IdCell,8 PaymentMethodCell,9 PriceCell,10 ProductCell,11 PurchaseDateCell,12} from "./components.jsx";13import { useClientDataSource, Grid } from "@1771technologies/lytenyte-pro";14import { useState } from "react";15import { ViewportShadows } from "@1771technologies/lytenyte-pro/components";16
17export interface GridSpec {18 readonly data: OrderData;19}20
21const columns: Grid.Column<GridSpec>[] = [22 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },23 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },24 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },25 {26 id: "customer",27 cellRenderer: AvatarCell,28 width: 180,29 name: "Customer",30 editable: true,31 editRenderer: TextCellEditor,32 },33 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130, editable: true },34 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },35 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email", editable: true },36];37
38export default function CellEditingDemo() {39 const [data, setData] = useState(initialData);40 const ds = useClientDataSource({41 data,42 onRowDataChange: ({ center }) => {43 setData((prev) => {44 const next = prev.map((row, i) => {45 if (center.has(i)) return center.get(i)!;46 return row;47 });48
49 return next as OrderData[];50 });51 },52 });53
54 return (55 <div className="ln-grid" style={{ height: 500 }}>56 <Grid rowHeight={50} columns={columns} rowSource={ds} slotShadows={ViewportShadows} editMode="cell" />57 </div>58 );59}60
61function TextCellEditor({ changeValue, editValue }: Grid.T.EditParams<GridSpec>) {62 return (63 <input64 className="focus:outline-ln-primary-50 h-full w-full px-2"65 value={`${editValue}`}66 onChange={(e) => changeValue(e.target.value)}67 />68 );69}1import { format } from "date-fns";2import { type JSX, type ReactNode } from "react";3import type { Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5
6export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {7 if (!api.rowIsLeaf(row) || !row.data) return;8
9 const url = row.data?.productThumbnail;10 const title = row.data.product;11 const desc = row.data.productDescription;12
13 return (14 <div className="flex h-full w-full items-center gap-2">15 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />16 <div className="text-ln-text-dark flex flex-col gap-0.5">17 <div className="font-semibold">{title}</div>18 <div className="text-ln-text-light text-xs">{desc}</div>19 </div>20 </div>21 );22}23
24export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {25 if (!api.rowIsLeaf(row) || !row.data) return;26
27 const url = row.data?.customerAvatar;28
29 const name = row.data.customer;30
31 return (32 <div className="flex h-full w-full items-center gap-2">33 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />34 <div className="text-ln-text-dark flex flex-col gap-0.5">35 <div>{name}</div>36 </div>37 </div>38 );39}40
41const formatter = new Intl.NumberFormat("en-Us", {42 minimumFractionDigits: 2,43 maximumFractionDigits: 2,44});45export function PriceCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {46 if (!api.rowIsLeaf(row) || !row.data) return;47
48 const price = formatter.format(row.data.price);49 const [dollars, cents] = price.split(".");50
51 return (52 <div className="flex h-full w-full items-center justify-end">53 <div className="flex items-baseline tabular-nums">54 <span className="text-ln-text font-semibold">${dollars}</span>.55 <span className="relative text-xs">{cents}</span>56 </div>57 </div>58 );59}60
61export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {62 if (!api.rowIsLeaf(row) || !row.data) return;63
64 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");65
66 return <div className="flex h-full w-full items-center">{formattedDate}</div>;67}68export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {69 if (!api.rowIsLeaf(row) || !row.data) return;70
71 return <div className="text-xs tabular-nums">{row.data.id}</div>;72}73
74export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {75 if (!api.rowIsLeaf(row) || !row.data) return;76
77 const cardNumber = row.data.cardNumber;78 const provider = row.data.paymentMethod;79
80 let Logo: ReactNode = null;81 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;82 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;83
84 return (85 <div className="flex h-full w-full items-center gap-2">86 <div className="flex w-7 items-center justify-center">{Logo}</div>87 <div className="flex items-center gap-px">88 <div className="bg-ln-gray-40 size-2 rounded-full"></div>89 <div className="bg-ln-gray-40 size-2 rounded-full"></div>90 <div className="bg-ln-gray-40 size-2 rounded-full"></div>91 <div className="bg-ln-gray-40 size-2 rounded-full"></div>92 </div>93 <div className="tabular-nums">{cardNumber}</div>94 </div>95 );96}97
98export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {99 if (!api.rowIsLeaf(row) || !row.data) return;100
101 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;102}103
104const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (105 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>106 <path107 fill="#1434cb"108 d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707 25.178 117.653h-67.454z"109 />110 </svg>111);112
113const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (114 <svg115 xmlns="http://www.w3.org/2000/svg"116 width={2500}117 height={1524}118 viewBox="55.2 38.3 464.5 287.8"119 {...props}120 >121 <path122 fill="#f79f1a"123 d="M519.7 182.2c0 79.5-64.3 143.9-143.6 143.9s-143.6-64.4-143.6-143.9S296.7 38.3 376 38.3s143.7 64.4 143.7 143.9z"124 />125 <path126 fill="#ea001b"127 d="M342.4 182.2c0 79.5-64.3 143.9-143.6 143.9S55.2 261.7 55.2 182.2 119.5 38.3 198.8 38.3s143.6 64.4 143.6 143.9z"128 />129 <path130 fill="#ff5f01"131 d="M287.4 68.9c-33.5 26.3-55 67.3-55 113.3s21.5 87 55 113.3c33.5-26.3 55-67.3 55-113.3s-21.5-86.9-55-113.3z"132 />133 </svg>134);The text editor in the demo uses the controlled pattern to manage the input value. Since the editor edits text, it does not require custom parsing. The code below updates the value directly:
1function TextCellEditor({ changeValue, editValue }: Grid.T.EditParams<GridSpec>) {2 return (3 <input4 className="focus:outline-ln-primary-50 h-full w-full px-2"5 value={`${editValue}`}6 onChange={(e) => changeValue(e.target.value)}7 />8 );9}Number Editors
Number edit renderers can use an input with type number, but text inputs
with custom number parsing offers better control over the input experience. The
demo below uses Ark UI’s number input,
which enforces numeric-only values, with improved number input editing.
Number Edit Renderer
23 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import type { OrderData } from "@1771technologies/grid-sample-data/orders";3import { data as initialData } from "@1771technologies/grid-sample-data/orders";4import {5 AvatarCell,6 EmailCell,7 IdCell,8 PaymentMethodCell,9 PriceCell,10 ProductCell,11 PurchaseDateCell,12} from "./components.jsx";13import { useClientDataSource, Grid } from "@1771technologies/lytenyte-pro";14import { useState } from "react";15import { NumberInput } from "@ark-ui/react/number-input";16import { twMerge } from "tailwind-merge";17import { clsx, type ClassValue } from "clsx";18import { ViewportShadows } from "@1771technologies/lytenyte-pro/components";19
20export interface GridSpec {21 readonly data: OrderData;22}23
24const columns: Grid.Column<GridSpec>[] = [25 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },26 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },27 {28 id: "price",29 type: "number",30 cellRenderer: PriceCell,31 width: 100,32 name: "Price",33 editable: true,34 editRenderer: NumberEditor,35 },36 {37 id: "customer",38 cellRenderer: AvatarCell,39 width: 180,40 name: "Customer",41 },42 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130, editable: true },43 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },44 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email", editable: true },45];46
47export default function CellEditingDemo() {48 const [data, setData] = useState(initialData);49 const ds = useClientDataSource({50 data,51 onRowDataChange: ({ center }) => {52 setData((prev) => {53 const next = prev.map((row, i) => {54 if (center.has(i)) return center.get(i)!;55 return row;56 });57
58 return next as OrderData[];59 });60 },61 });62
63 return (64 <div className="ln-grid" style={{ height: 500 }}>65 <Grid rowHeight={50} columns={columns} rowSource={ds} slotShadows={ViewportShadows} editMode="cell" />66 </div>67 );68}69
70function NumberEditor(props: Grid.T.EditParams<GridSpec>) {71 const { changeValue, editValue, editValidation, column } = props;72 const valid = typeof editValidation === "boolean" ? editValidation : !editValidation[column.id];73
74 return (75 <>76 <NumberInput.Root77 className={tw(78 "focus-within:outline-ln-primary-50 flex h-full w-full items-center rounded px-2 focus-within:outline focus-within:-outline-offset-1",79 !valid && "bg-ln-red-30 focus-within:outline-ln-red-50",80 )}81 value={`${editValue}`}82 onValueChange={(d) => {83 changeValue(Number.isNaN(d.valueAsNumber) ? 0 : d.valueAsNumber);84 }}85 onKeyDown={(e) => {86 if (e.key === "," || e.key === "-") {87 e.preventDefault();88 e.stopPropagation();89 }90 }}91 min={0}92 allowOverflow={false}93 >94 <NumberInput.Input className={tw("h-full w-full flex-1 focus:outline-none")} />95 </NumberInput.Root>96 </>97 );98}99
100export function tw(...c: ClassValue[]) {101 return twMerge(clsx(...c));102}1import { format } from "date-fns";2import { type JSX, type ReactNode } from "react";3import type { Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5
6export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {7 if (!api.rowIsLeaf(row) || !row.data) return;8
9 const url = row.data?.productThumbnail;10 const title = row.data.product;11 const desc = row.data.productDescription;12
13 return (14 <div className="flex h-full w-full items-center gap-2">15 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />16 <div className="text-ln-text-dark flex flex-col gap-0.5">17 <div className="font-semibold">{title}</div>18 <div className="text-ln-text-light text-xs">{desc}</div>19 </div>20 </div>21 );22}23
24export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {25 if (!api.rowIsLeaf(row) || !row.data) return;26
27 const url = row.data?.customerAvatar;28
29 const name = row.data.customer;30
31 return (32 <div className="flex h-full w-full items-center gap-2">33 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />34 <div className="text-ln-text-dark flex flex-col gap-0.5">35 <div>{name}</div>36 </div>37 </div>38 );39}40
41const formatter = new Intl.NumberFormat("en-Us", {42 minimumFractionDigits: 2,43 maximumFractionDigits: 2,44});45export function PriceCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {46 if (!api.rowIsLeaf(row) || !row.data) return;47
48 const price = formatter.format(row.data.price);49 const [dollars, cents] = price.split(".");50
51 return (52 <div className="flex h-full w-full items-center justify-end">53 <div className="flex items-baseline tabular-nums">54 <span className="text-ln-text font-semibold">${dollars}</span>.55 <span className="relative text-xs">{cents}</span>56 </div>57 </div>58 );59}60
61export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {62 if (!api.rowIsLeaf(row) || !row.data) return;63
64 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");65
66 return <div className="flex h-full w-full items-center">{formattedDate}</div>;67}68export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {69 if (!api.rowIsLeaf(row) || !row.data) return;70
71 return <div className="text-xs tabular-nums">{row.data.id}</div>;72}73
74export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {75 if (!api.rowIsLeaf(row) || !row.data) return;76
77 const cardNumber = row.data.cardNumber;78 const provider = row.data.paymentMethod;79
80 let Logo: ReactNode = null;81 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;82 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;83
84 return (85 <div className="flex h-full w-full items-center gap-2">86 <div className="flex w-7 items-center justify-center">{Logo}</div>87 <div className="flex items-center gap-px">88 <div className="bg-ln-gray-40 size-2 rounded-full"></div>89 <div className="bg-ln-gray-40 size-2 rounded-full"></div>90 <div className="bg-ln-gray-40 size-2 rounded-full"></div>91 <div className="bg-ln-gray-40 size-2 rounded-full"></div>92 </div>93 <div className="tabular-nums">{cardNumber}</div>94 </div>95 );96}97
98export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {99 if (!api.rowIsLeaf(row) || !row.data) return;100
101 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;102}103
104const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (105 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>106 <path107 fill="#1434cb"108 d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707 25.178 117.653h-67.454z"109 />110 </svg>111);112
113const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (114 <svg115 xmlns="http://www.w3.org/2000/svg"116 width={2500}117 height={1524}118 viewBox="55.2 38.3 464.5 287.8"119 {...props}120 >121 <path122 fill="#f79f1a"123 d="M519.7 182.2c0 79.5-64.3 143.9-143.6 143.9s-143.6-64.4-143.6-143.9S296.7 38.3 376 38.3s143.7 64.4 143.7 143.9z"124 />125 <path126 fill="#ea001b"127 d="M342.4 182.2c0 79.5-64.3 143.9-143.6 143.9S55.2 261.7 55.2 182.2 119.5 38.3 198.8 38.3s143.6 64.4 143.6 143.9z"128 />129 <path130 fill="#ff5f01"131 d="M287.4 68.9c-33.5 26.3-55 67.3-55 113.3s21.5 87 55 113.3c33.5-26.3 55-67.3 55-113.3s-21.5-86.9-55-113.3z"132 />133 </svg>134);Date Editors
Editing requirements for dates vary from simple selections to complex ranges
and timezone adjustments. For simple edits, the native browser’s date input
is an efficient choice. Double-click a cell in the Purchase Date column to start editing.
Date Edit Renderer
21 collapsed lines
1import "@1771technologies/lytenyte-pro/light-dark.css";2import type { OrderData } from "@1771technologies/grid-sample-data/orders";3import { data as initialData } from "@1771technologies/grid-sample-data/orders";4import {5 AvatarCell,6 EmailCell,7 IdCell,8 PaymentMethodCell,9 PriceCell,10 ProductCell,11 PurchaseDateCell,12} from "./components.jsx";13import { useClientDataSource, Grid } from "@1771technologies/lytenyte-pro";14import { useState } from "react";15import { format } from "date-fns";16import { ViewportShadows } from "@1771technologies/lytenyte-pro/components";17
18export interface GridSpec {19 readonly data: OrderData;20}21
22const columns: Grid.Column<GridSpec>[] = [23 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },24 { id: "product", cellRenderer: ProductCell, width: 200, name: "Product" },25 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },26 { id: "customer", cellRenderer: AvatarCell, width: 180, name: "Customer" },27 {28 id: "purchaseDate",29 cellRenderer: PurchaseDateCell,30 name: "Purchase Date",31 width: 150,32 editable: true,33 editRenderer: DateCellEditor,34 editSetter: ({ editValue, column, editData }) => {35 let currentValue!: string;36 try {37 currentValue = typeof editValue !== "string" ? "" : format(editValue, "yyyy-MM-dd");38 } catch {39 currentValue = "";40 }41
42 const data = { ...(editData as Record<string, unknown>), [column.id]: currentValue };43
44 return data;45 },46 },47 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },48 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email", editable: true },49];50
51export default function CellEditingDemo() {52 const [data, setData] = useState(initialData);53 const ds = useClientDataSource({54 data,55 onRowDataChange: ({ center }) => {56 setData((prev) => {57 const next = prev.map((row, i) => {58 if (center.has(i)) return center.get(i)!;59 return row;60 });61
62 return next as OrderData[];63 });64 },65 });66
67 return (68 <div className="ln-grid" style={{ height: 500 }}>69 <Grid rowHeight={50} columns={columns} rowSource={ds} slotShadows={ViewportShadows} editMode="cell" />70 </div>71 );72}73
74function DateCellEditor({ changeValue, editValue }: Grid.T.EditParams<GridSpec>) {75 const formatted = typeof editValue === "string" && editValue ? format(editValue, "yyyy-MM-dd") : "";76 return (77 <input78 className="focus:outline-ln-primary-50 h-full w-full px-2"79 defaultValue={formatted}80 type="date"81 onChange={(e) => {82 const next = new Date(e.target.value);83 try {84 changeValue(format(next, "yyyy-MM-dd"));85 } catch {86 return;87 }88 }}89 />90 );91}1import { format, isValid } from "date-fns";2import { type JSX, type ReactNode } from "react";3import type { Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5
6export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {7 if (!api.rowIsLeaf(row) || !row.data) return;8
9 const url = row.data?.productThumbnail;10 const title = row.data.product;11 const desc = row.data.productDescription;12
13 return (14 <div className="flex h-full w-full items-center gap-2">15 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />16 <div className="text-ln-text-dark flex flex-col gap-0.5">17 <div className="font-semibold">{title}</div>18 <div className="text-ln-text-light text-xs">{desc}</div>19 </div>20 </div>21 );22}23
24export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {25 if (!api.rowIsLeaf(row) || !row.data) return;26
27 const url = row.data?.customerAvatar;28
29 const name = row.data.customer;30
31 return (32 <div className="flex h-full w-full items-center gap-2">33 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />34 <div className="text-ln-text-dark flex flex-col gap-0.5">35 <div>{name}</div>36 </div>37 </div>38 );39}40
41const formatter = new Intl.NumberFormat("en-Us", {42 minimumFractionDigits: 2,43 maximumFractionDigits: 2,44});45export function PriceCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {46 if (!api.rowIsLeaf(row) || !row.data) return;47
48 const price = formatter.format(row.data.price);49 const [dollars, cents] = price.split(".");50
51 return (52 <div className="flex h-full w-full items-center justify-end">53 <div className="flex items-baseline tabular-nums">54 <span className="text-ln-text font-semibold">${dollars}</span>.55 <span className="relative text-xs">{cents}</span>56 </div>57 </div>58 );59}60
61export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {62 if (!api.rowIsLeaf(row) || !row.data) return;63
64 const dateField = new Date(row.data.purchaseDate);65
66 if (!isValid(dateField)) return <div className="flex h-full w-full items-center">-</div>;67
68 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");69
70 return <div className="flex h-full w-full items-center">{formattedDate}</div>;71}72export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {73 if (!api.rowIsLeaf(row) || !row.data) return;74
75 return <div className="text-xs tabular-nums">{row.data.id}</div>;76}77
78export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {79 if (!api.rowIsLeaf(row) || !row.data) return;80
81 const cardNumber = row.data.cardNumber;82 const provider = row.data.paymentMethod;83
84 let Logo: ReactNode = null;85 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;86 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;87
88 return (89 <div className="flex h-full w-full items-center gap-2">90 <div className="flex w-7 items-center justify-center">{Logo}</div>91 <div className="flex items-center gap-px">92 <div className="bg-ln-gray-40 size-2 rounded-full"></div>93 <div className="bg-ln-gray-40 size-2 rounded-full"></div>94 <div className="bg-ln-gray-40 size-2 rounded-full"></div>95 <div className="bg-ln-gray-40 size-2 rounded-full"></div>96 </div>97 <div className="tabular-nums">{cardNumber}</div>98 </div>99 );100}101
102export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {103 if (!api.rowIsLeaf(row) || !row.data) return;104
105 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;106}107
108const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (109 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>110 <path111 fill="#1434cb"112 d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707 25.178 117.653h-67.454z"113 />114 </svg>115);116
117const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (118 <svg119 xmlns="http://www.w3.org/2000/svg"120 width={2500}121 height={1524}122 viewBox="55.2 38.3 464.5 287.8"123 {...props}124 >125 <path126 fill="#f79f1a"127 d="M519.7 182.2c0 79.5-64.3 143.9-143.6 143.9s-143.6-64.4-143.6-143.9S296.7 38.3 376 38.3s143.7 64.4 143.7 143.9z"128 />129 <path130 fill="#ea001b"131 d="M342.4 182.2c0 79.5-64.3 143.9-143.6 143.9S55.2 261.7 55.2 182.2 119.5 38.3 198.8 38.3s143.6 64.4 143.6 143.9z"132 />133 <path134 fill="#ff5f01"135 d="M287.4 68.9c-33.5 26.3-55 67.3-55 113.3s21.5 87 55 113.3c33.5-26.3 55-67.3 55-113.3s-21.5-86.9-55-113.3z"136 />137 </svg>138);The date edit renderer uses a native date input as an uncontrolled
component instead of a controlled one.
Controlling browser date inputs with React state can cause invalid states
during manual keyboard entry. To avoid this, keep the date input
uncontrolled and track changes. The browser enforces date validity,
and the edit state reflects the latest value.
1function DateCellEditor({ changeValue, editValue }: Grid.T.EditParams<GridSpec>) {2 const formatted = typeof editValue === "string" && editValue ? format(editValue, "yyyy-MM-dd") : "";3 return (4 <input5 className="focus:outline-ln-primary-50 h-full w-full px-2"6 defaultValue={formatted}7 type="date"8 onChange={(e) => {9 const next = new Date(e.target.value);10 try {11 changeValue(format(next, "yyyy-MM-dd"));12 } catch {13 return;14 }15 }}16 />17 );18}Popover Editors
For columns with a fixed set of values, a dropdown list is a common
editing pattern. While the native select element is the simplest option, it has several limitations:
- Programmatic Control: You can’t force the
selectmenu to open when editing begins; users must press Space or Enter. - Limited Styling:
optionelements support minimal CSS customization, which makes consistent styling harder. - Poor Scalability:
selectelement works best with a small set of options and becomes impractical for large lists of values.
To see the native select element in action, double-click any cell in
the Product column below to begin editing.
Native Select Popover
20 collapsed lines
1import "@1771technologies/lytenyte-pro/components.css";2import "@1771technologies/lytenyte-pro/light-dark.css";3import type { OrderData } from "@1771technologies/grid-sample-data/orders";4import { data as initialData } from "@1771technologies/grid-sample-data/orders";5import {6 AvatarCell,7 EmailCell,8 IdCell,9 PaymentMethodCell,10 PriceCell,11 ProductCell,12 PurchaseDateCell,13} from "./components.jsx";14import { useClientDataSource, Grid } from "@1771technologies/lytenyte-pro";15import { useState } from "react";16
17export interface GridSpec {18 readonly data: OrderData;19}20
21const columns: Grid.Column<GridSpec>[] = [22 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },23 {24 id: "product",25 cellRenderer: ProductCell,26 width: 200,27 name: "Product",28 editable: true,29 editOnPrintable: false,30 editRenderer: ProductSelect,31 },32 { id: "price", type: "number", cellRenderer: PriceCell, width: 100, name: "Price" },33 { id: "customer", cellRenderer: AvatarCell, width: 180, name: "Customer" },34 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130 },35 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },36 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email", editable: true },37];38
39export default function CellEditingDemo() {40 const [data, setData] = useState(initialData);41 const ds = useClientDataSource({42 data,43 onRowDataChange: ({ center }) => {44 setData((prev) => {45 const next = prev.map((row, i) => {46 if (center.has(i)) return center.get(i)!;47 return row;48 });49
50 return next as OrderData[];51 });52 },53 });54
55 return (56 <div className="ln-grid" style={{ height: 500 }}>57 <Grid rowHeight={50} columns={columns} rowSource={ds} editMode="cell" />58 </div>59 );60}61
62const options = initialData.map((x) => ({63 id: x.product,64 product: x.product,65 productThumbnail: x.productThumbnail,66 price: x.price,67 productDescription: x.productDescription,68}));69
70function ProductSelect({ changeData, editValue, editData, commit }: Grid.T.EditParams<GridSpec>) {71 const value = options.find((x) => x.id === editValue)!;72
73 return (74 <select75 className="h-full w-full"76 value={value.id}77 onChange={(e) => {78 const p = options.find((x) => x.id === e.target.value);79 if (!p) return;80
81 changeData({82 ...(editData as Record<string, unknown>),83 product: p.product,84 price: p.price,85 productThumbnail: p.productThumbnail,86 productDescription: p.productDescription,87 });88 commit();89 }}90 >91 {options.map((x) => {92 return <option value={x.id}>{x.product}</option>;93 })}94 </select>95 );96}1import { format } from "date-fns";2import { type JSX, type ReactNode } from "react";3import { type Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5
6export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {7 if (!api.rowIsLeaf(row) || !row.data) return;8
9 const url = row.data?.productThumbnail;10 const title = row.data.product;11 const desc = row.data.productDescription;12
13 return (14 <div className="flex h-full w-full items-center gap-2">15 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />16 <div className="text-ln-text-dark flex flex-col gap-0.5">17 <div className="font-semibold">{title}</div>18 <div className="text-ln-text-light text-xs">{desc}</div>19 </div>20 </div>21 );22}23
24export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {25 if (!api.rowIsLeaf(row) || !row.data) return;26
27 const url = row.data?.customerAvatar;28
29 const name = row.data.customer;30
31 return (32 <div className="flex h-full w-full items-center gap-2">33 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />34 <div className="text-ln-text-dark flex flex-col gap-0.5">35 <div>{name}</div>36 </div>37 </div>38 );39}40
41const formatter = new Intl.NumberFormat("en-Us", {42 minimumFractionDigits: 2,43 maximumFractionDigits: 2,44});45export function PriceCell({ api, row, editData }: Grid.T.CellRendererParams<GridSpec>) {46 if (!api.rowIsLeaf(row) || !row.data) return;47
48 const data = (editData as Record<string, number>) ?? row.data;49
50 const price = formatter.format(data.price);51 const [dollars, cents] = price.split(".");52
53 return (54 <div className={"flex h-full w-full items-center justify-end"}>55 <div className="flex items-baseline tabular-nums">56 <span className="text-ln-text font-semibold">${dollars}</span>.57 <span className="relative text-xs">{cents}</span>58 </div>59 </div>60 );61}62
63export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {64 if (!api.rowIsLeaf(row) || !row.data) return;65
66 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");67
68 return <div className="flex h-full w-full items-center">{formattedDate}</div>;69}70export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {71 if (!api.rowIsLeaf(row) || !row.data) return;72
73 return <div className="text-xs tabular-nums">{row.data.id}</div>;74}75
76export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {77 if (!api.rowIsLeaf(row) || !row.data) return;78
79 const cardNumber = row.data.cardNumber;80 const provider = row.data.paymentMethod;81
82 let Logo: ReactNode = null;83 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;84 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;85
86 return (87 <div className="flex h-full w-full items-center gap-2">88 <div className="flex w-7 items-center justify-center">{Logo}</div>89 <div className="flex items-center gap-px">90 <div className="bg-ln-gray-40 size-2 rounded-full"></div>91 <div className="bg-ln-gray-40 size-2 rounded-full"></div>92 <div className="bg-ln-gray-40 size-2 rounded-full"></div>93 <div className="bg-ln-gray-40 size-2 rounded-full"></div>94 </div>95 <div className="tabular-nums">{cardNumber}</div>96 </div>97 );98}99
100export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {101 if (!api.rowIsLeaf(row) || !row.data) return;102
103 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;104}105
106const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (107 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>108 <path109 fill="#1434cb"110 d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707 25.178 117.653h-67.454z"111 />112 </svg>113);114
115const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (116 <svg117 xmlns="http://www.w3.org/2000/svg"118 width={2500}119 height={1524}120 viewBox="55.2 38.3 464.5 287.8"121 {...props}122 >123 <path124 fill="#f79f1a"125 d="M519.7 182.2c0 79.5-64.3 143.9-143.6 143.9s-143.6-64.4-143.6-143.9S296.7 38.3 376 38.3s143.7 64.4 143.7 143.9z"126 />127 <path128 fill="#ea001b"129 d="M342.4 182.2c0 79.5-64.3 143.9-143.6 143.9S55.2 261.7 55.2 182.2 119.5 38.3 198.8 38.3s143.6 64.4 143.6 143.9z"130 />131 <path132 fill="#ff5f01"133 d="M287.4 68.9c-33.5 26.3-55 67.3-55 113.3s21.5 87 55 113.3c33.5-26.3 55-67.3 55-113.3s-21.5-86.9-55-113.3z"134 />135 </svg>136);Given the drawbacks of the select element, a better alternative is to use a combobox
or custom select component.
The demo below demonstrates this using LyteNyte Grid’s Smart Select component. Double-click any cell in the Product column below to begin editing.
Smart Select Popover
21 collapsed lines
1import "@1771technologies/lytenyte-pro/components.css";2import "@1771technologies/lytenyte-pro/light-dark.css";3import type { OrderData } from "@1771technologies/grid-sample-data/orders";4import { data as initialData } from "@1771technologies/grid-sample-data/orders";5import {6 AvatarCell,7 EmailCell,8 IdCell,9 PaymentMethodCell,10 PriceCell,11 ProductCell,12 PurchaseDateCell,13} from "./components.jsx";14import { useClientDataSource, Grid } from "@1771technologies/lytenyte-pro";15import { useState } from "react";16import { SmartSelect, ViewportShadows } from "@1771technologies/lytenyte-pro/components";17
18export interface GridSpec {19 readonly data: OrderData;20}21
22const columns: Grid.Column<GridSpec>[] = [23 { id: "id", width: 60, widthMin: 60, cellRenderer: IdCell, name: "ID" },24 {25 id: "product",26 cellRenderer: ProductCell,27 width: 200,28 name: "Product",29 editable: true,30 editOnPrintable: false,31 editRenderer: ProductSelect,32 },33 {34 id: "price",35 type: "number",36 cellRenderer: PriceCell,37 width: 100,38 name: "Price",39 editMutateCommit: (p) => {40 // You will want to validate with Zod.41 const data = p.editData as any;42 const value = String(data[p.column.id]);43
44 // Parse the commit value to a string.45 const numberValue = Number.parseFloat(value);46 if (Number.isNaN(numberValue)) data[p.column.id] = null;47 else data[p.column.id] = numberValue;48 },49 editable: true,50 editRenderer: NumberEditor,51 },52 {53 id: "customer",54 cellRenderer: AvatarCell,55 width: 180,56 name: "Customer",57 },58 { id: "purchaseDate", cellRenderer: PurchaseDateCell, name: "Purchase Date", width: 130 },59 { id: "paymentMethod", cellRenderer: PaymentMethodCell, name: "Payment Method", width: 150 },60 { id: "email", cellRenderer: EmailCell, width: 220, name: "Email", editable: true },61];62
63export default function CellEditingDemo() {64 const [data, setData] = useState(initialData);65 const ds = useClientDataSource({66 data,67 onRowDataChange: ({ center }) => {68 setData((prev) => {69 const next = prev.map((row, i) => {70 if (center.has(i)) return center.get(i)!;71 return row;72 });73
74 return next as OrderData[];75 });76 },77 });78
79 return (80 <div className="ln-grid" style={{ height: 500 }}>81 <Grid rowHeight={50} columns={columns} rowSource={ds} slotShadows={ViewportShadows} editMode="cell" />82 </div>83 );84}85
86const options = initialData.map((x) => ({87 id: x.product,88 product: x.product,89 productThumbnail: x.productThumbnail,90 price: x.price,91 productDescription: x.productDescription,92}));93
94function ProductSelect({ changeData, editValue, editData, commit }: Grid.T.EditParams<GridSpec>) {95 const value = options.find((x) => x.id === editValue) ?? null;96
97 const [open, setOpen] = useState(false);98
99 return (100 <SmartSelect101 kind="basic"102 options={options}103 open={open}104 onOpenChange={setOpen}105 onOpenChangeComplete={(b) => {106 if (!b) commit();107 }}108 openKeys={[" ", "ArrowDown"]}109 container={<SmartSelect.Container className="max-h-50 overflow-auto" />}110 trigger={111 <SmartSelect.BasicTrigger112 className="focus:outline-ln-primary-50 flex h-full w-full items-center gap-2 focus:outline focus:-outline-offset-1"113 autoFocus114 onFocus={() => setOpen(true)}115 >116 {value?.productThumbnail && (117 <img118 className="border-ln-border-strong h-7 w-7 rounded-lg border"119 src={value.productThumbnail}120 alt={value.id}121 />122 )}123 {editValue as string}124 </SmartSelect.BasicTrigger>125 }126 value={value}127 onOptionChange={(p) => {128 if (p) {129 changeData({130 ...(editData as Record<string, unknown>),131 product: p.product,132 price: p.price,133 productThumbnail: p.productThumbnail,134 productDescription: p.productDescription,135 });136 setOpen(false);137 }138 }}139 >140 {(p) => {141 return (142 <SmartSelect.Option key={p.option.id} {...p} className="flex items-center gap-2">143 <img144 className="border-ln-border-strong flex h-7 w-7 rounded-lg border"145 src={p.option.productThumbnail}146 alt={p.option.id}147 />148 <div>{p.option.product}</div>149 </SmartSelect.Option>150 );151 }}152 </SmartSelect>153 );154}155
156function NumberEditor({ changeValue, editValue }: Grid.T.EditParams<GridSpec>) {157 return (158 <input159 className="focus:outline-ln-primary-50 h-full w-full px-2"160 value={`${editValue}`}161 type="number"162 onChange={(e) => changeValue(e.target.value)}163 />164 );165}1import { format } from "date-fns";2import { type JSX, type ReactNode } from "react";3import { type Grid } from "@1771technologies/lytenyte-pro";4import type { GridSpec } from "./demo.jsx";5
6export function ProductCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {7 if (!api.rowIsLeaf(row) || !row.data) return;8
9 const url = row.data?.productThumbnail;10 const title = row.data.product;11 const desc = row.data.productDescription;12
13 return (14 <div className="flex h-full w-full items-center gap-2">15 <img className="border-ln-border-strong h-7 w-7 rounded-lg border" src={url} alt={title + desc} />16 <div className="text-ln-text-dark flex flex-col gap-0.5">17 <div className="font-semibold">{title}</div>18 <div className="text-ln-text-light text-xs">{desc}</div>19 </div>20 </div>21 );22}23
24export function AvatarCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {25 if (!api.rowIsLeaf(row) || !row.data) return;26
27 const url = row.data?.customerAvatar;28
29 const name = row.data.customer;30
31 return (32 <div className="flex h-full w-full items-center gap-2">33 <img className="border-ln-border-strong h-7 w-7 rounded-full border" src={url} alt={name} />34 <div className="text-ln-text-dark flex flex-col gap-0.5">35 <div>{name}</div>36 </div>37 </div>38 );39}40
41const formatter = new Intl.NumberFormat("en-Us", {42 minimumFractionDigits: 2,43 maximumFractionDigits: 2,44});45export function PriceCell({ api, row, editData }: Grid.T.CellRendererParams<GridSpec>) {46 if (!api.rowIsLeaf(row) || !row.data) return;47
48 const data = (editData as Record<string, number>) ?? row.data;49
50 if (data.price == null) return "-";51
52 const price = formatter.format(data.price);53 const [dollars, cents] = price.split(".");54
55 return (56 <div className={"flex h-full w-full items-center justify-end"}>57 <div className="flex items-baseline tabular-nums">58 <span className="text-ln-text font-semibold">${dollars}</span>.59 <span className="relative text-xs">{cents}</span>60 </div>61 </div>62 );63}64
65export function PurchaseDateCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {66 if (!api.rowIsLeaf(row) || !row.data) return;67
68 const formattedDate = format(row.data.purchaseDate, "dd MMM, yyyy");69
70 return <div className="flex h-full w-full items-center">{formattedDate}</div>;71}72export function IdCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {73 if (!api.rowIsLeaf(row) || !row.data) return;74
75 return <div className="text-xs tabular-nums">{row.data.id}</div>;76}77
78export function PaymentMethodCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {79 if (!api.rowIsLeaf(row) || !row.data) return;80
81 const cardNumber = row.data.cardNumber;82 const provider = row.data.paymentMethod;83
84 let Logo: ReactNode = null;85 if (provider === "Visa") Logo = <VisaLogo className="w-6" />;86 if (provider === "Mastercard") Logo = <MastercardLogo className="w-6" />;87
88 return (89 <div className="flex h-full w-full items-center gap-2">90 <div className="flex w-7 items-center justify-center">{Logo}</div>91 <div className="flex items-center gap-px">92 <div className="bg-ln-gray-40 size-2 rounded-full"></div>93 <div className="bg-ln-gray-40 size-2 rounded-full"></div>94 <div className="bg-ln-gray-40 size-2 rounded-full"></div>95 <div className="bg-ln-gray-40 size-2 rounded-full"></div>96 </div>97 <div className="tabular-nums">{cardNumber}</div>98 </div>99 );100}101
102export function EmailCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {103 if (!api.rowIsLeaf(row) || !row.data) return;104
105 return <div className="text-ln-primary-50 flex h-full w-full items-center">{row.data.email}</div>;106}107
108const VisaLogo = (props: JSX.IntrinsicElements["svg"]) => (109 <svg xmlns="http://www.w3.org/2000/svg" width={2500} height={812} viewBox="0.5 0.5 999 323.684" {...props}>110 <path111 fill="#1434cb"112 d="M651.185.5c-70.933 0-134.322 36.766-134.322 104.694 0 77.9 112.423 83.28 112.423 122.415 0 16.478-18.884 31.229-51.137 31.229-45.773 0-79.984-20.611-79.984-20.611l-14.638 68.547s39.41 17.41 91.734 17.41c77.552 0 138.576-38.572 138.576-107.66 0-82.316-112.89-87.537-112.89-123.86 0-12.91 15.501-27.053 47.662-27.053 36.286 0 65.892 14.99 65.892 14.99l14.326-66.204S696.614.5 651.185.5zM2.218 5.497.5 15.49s29.842 5.461 56.719 16.356c34.606 12.492 37.072 19.765 42.9 42.353l63.51 244.832h85.138L379.927 5.497h-84.942L210.707 218.67l-34.39-180.696c-3.154-20.68-19.13-32.477-38.685-32.477H2.218zm411.865 0L347.449 319.03h80.999l66.4-313.534h-80.765zm451.759 0c-19.532 0-29.88 10.457-37.474 28.73L709.699 319.03h84.942l16.434-47.468h103.483l9.994 47.468H999.5L934.115 5.497h-68.273zm11.047 84.707 25.178 117.653h-67.454z"113 />114 </svg>115);116
117const MastercardLogo = (props: JSX.IntrinsicElements["svg"]) => (118 <svg119 xmlns="http://www.w3.org/2000/svg"120 width={2500}121 height={1524}122 viewBox="55.2 38.3 464.5 287.8"123 {...props}124 >125 <path126 fill="#f79f1a"127 d="M519.7 182.2c0 79.5-64.3 143.9-143.6 143.9s-143.6-64.4-143.6-143.9S296.7 38.3 376 38.3s143.7 64.4 143.7 143.9z"128 />129 <path130 fill="#ea001b"131 d="M342.4 182.2c0 79.5-64.3 143.9-143.6 143.9S55.2 261.7 55.2 182.2 119.5 38.3 198.8 38.3s143.6 64.4 143.6 143.9z"132 />133 <path134 fill="#ff5f01"135 d="M287.4 68.9c-33.5 26.3-55 67.3-55 113.3s21.5 87 55 113.3c33.5-26.3 55-67.3 55-113.3s-21.5-86.9-55-113.3z"136 />137 </svg>138);Note
LyteNyte Grid’s Smart Select component is only available to
users. However,
you can use another open-source combobox component. Below are some good options:
Popover Editor Considerations
When you create a custom popover, consider these points:
- Enter: LyteNyte Grid commits edits on Enter. Many comboboxes use Enter to open the completion menu. Decide which action Enter triggers while editing.
- ArrowDown: LyteNyte Grid moves focus to the next cell on ArrowDown. Many select components use ArrowDown to open options. Decide which action ArrowDown triggers while editing.
- Clipping: Grid cells clip content that exceeds their bounds. To prevent your popover from
being clipped, use a React Portal to render it into the
bodyelement. - Input Triggers: By default, editing starts when a user presses a printable character.
If this isn’t ideal for your selection component, set
editOnPrintabletofalseon the column.
Next Steps
- Cell Editing: Edit cell values and commit updates in the grid.
- Cell Edit Validation: Validate cell edits to ensure updates are correct.
- Full Row Cell Editing: Simultaneously edit cells across an entire row.