Spreadsheet recipe
Custom styling
Combine spreadsheet mode, global search, stable row colors, and conditional cells.
SpreadsheetStylingSearch
Interactive, production component
tsx
import { useState, type ReactNode } from "react";
import { DataTable } from "../../Datatable";
import type { ColumnSetting, SearchMatch } from "../../Datatable";
import { products as initialProducts, type ProductRow } from "./exampleData";
import type { ExampleProps } from "./types";
function SearchAwareText({
text,
match,
}: {
text: string;
match?: SearchMatch;
}) {
if (!match?.ranges.length) return <>{text}</>;
const nodes: ReactNode[] = [];
let cursor = 0;
for (const [start, end] of match.ranges) {
if (start > cursor) nodes.push(text.slice(cursor, start));
nodes.push(
<mark key={`${start}-${end}`} className="dt-search-mark">
{text.slice(start, end)}
</mark>
);
cursor = end;
}
if (cursor < text.length) nodes.push(text.slice(cursor));
return <>{nodes}</>;
}
const columns: ColumnSetting<ProductRow>[] = [
{
id: "product",
title: "Product",
width: 220,
pinned: "left",
cellStyle: ({ row }) =>
row.featured
? { fontWeight: 700, color: "#0f172a" }
: undefined,
},
{ id: "brand", title: "Brand", width: 110 },
{
id: "status",
title: "Status",
width: 120,
headerAlign: "center",
align: "center",
searchValue: (row) => `${row.status} ${row.owner} ${row.region}`,
cellStyle: ({ row }) => {
if (row.status === "Paused") {
return {
backgroundColor: "#fae8ff",
color: "#86198f",
fontWeight: 700,
};
}
if (row.status === "Draft") {
return {
backgroundColor: "#fef3c7",
color: "#92400e",
fontWeight: 700,
};
}
return {
backgroundColor: "#dcfce7",
color: "#166534",
fontWeight: 700,
};
},
},
{
id: "stock",
title: "Stock",
width: 96,
align: "right",
filterType: "number",
cellStyle: ({ row }) =>
row.stock <= row.reorderAt
? {
backgroundColor: "#fee2e2",
color: "#991b1b",
fontWeight: 700,
}
: undefined,
},
{
id: "margin",
title: "Margin %",
width: 100,
align: "right",
cellStyle: ({ value }) =>
Number(value) < 30
? { color: "#b91c1c", fontWeight: 700 }
: Number(value) > 45
? { color: "#166534", fontWeight: 700 }
: undefined,
},
{
id: "owner",
title: "Owner",
width: 160,
cell: ({ value, searchMatch }) => (
<span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
<span
aria-hidden
style={{
width: 8,
height: 8,
borderRadius: 999,
background: "currentColor",
opacity: 0.5,
}}
/>
<SearchAwareText text={String(value)} match={searchMatch} />
</span>
),
},
{
id: "updatedAt",
title: "Updated",
width: 110,
enableGlobalFilter: false,
},
];
export default function StylingCustomExample({ theme }: ExampleProps) {
const [rows, setRows] = useState(() => [...initialProducts]);
return (
<DataTable
theme={theme}
mode="spreadsheet"
title="Custom spreadsheet styling"
dataSource={rows}
onChange={setRows}
columnSettings={columns}
maxHeight={500}
showGlobalSearch
enableEditing
enableColumnResizing
enableColumnReorder
enableColumnSettings
rowStyle={({ row }) => {
if (row.stock <= row.reorderAt) {
return {
backgroundColor: "#fff7ed",
color: "#9a3412",
};
}
if (row.featured) {
return {
backgroundColor: "#eff6ff",
};
}
return undefined;
}}
/>
);
}
Related API