feat: integrate AG Grid with Mantine theme and add showcase component

This commit is contained in:
Firman Ramdhani
2026-07-20 11:48:47 +07:00
parent 853769b1f8
commit d089c40707
10 changed files with 577 additions and 5 deletions
@@ -0,0 +1,218 @@
import { useState, useMemo } from 'react';
import { Card, Title, Text, Stack, Badge, Group, Select, NumberInput } from '@repo/ui/components';
import { AgGridProvider, DataGrid } from '@repo/ui/ag-grid';
import type { ColDef, ValueFormatterParams } from '@repo/ui/ag-grid';
/* ─── Sample Data ──────────────────────────────────────────────── */
interface OrderRow {
id: string;
orderCode: string;
customer: string;
email: string;
product: string;
quantity: number;
unitPrice: number;
total: number;
status: 'Draft' | 'Pending' | 'Processing' | 'Shipped' | 'Delivered' | 'Cancelled';
createdAt: string;
region: string;
}
const SAMPLE_DATA: OrderRow[] = [
{ id: '1', orderCode: 'ORD-2026-001', customer: 'Acme Corp', email: 'acme@example.com', product: 'Widget A', quantity: 120, unitPrice: 24.5, total: 2940, status: 'Delivered', createdAt: '2026-01-15', region: 'North America' },
{ id: '2', orderCode: 'ORD-2026-002', customer: 'Globex Inc', email: 'info@globex.com', product: 'Widget B', quantity: 50, unitPrice: 89.99, total: 4499.5, status: 'Shipped', createdAt: '2026-02-03', region: 'Europe' },
{ id: '3', orderCode: 'ORD-2026-003', customer: 'Initech', email: 'orders@initech.com', product: 'Gadget Pro', quantity: 200, unitPrice: 15.0, total: 3000, status: 'Processing', createdAt: '2026-03-21', region: 'Asia Pacific' },
{ id: '4', orderCode: 'ORD-2026-004', customer: 'Umbrella Ltd', email: 'sales@umbrella.co', product: 'Widget A', quantity: 75, unitPrice: 24.5, total: 1837.5, status: 'Pending', createdAt: '2026-04-10', region: 'Europe' },
{ id: '5', orderCode: 'ORD-2026-005', customer: 'Stark Industries', email: 'tony@stark.io', product: 'Gadget Elite', quantity: 10, unitPrice: 499.0, total: 4990, status: 'Delivered', createdAt: '2026-04-18', region: 'North America' },
{ id: '6', orderCode: 'ORD-2026-006', customer: 'Wayne Enterprises', email: 'bruce@wayne.com', product: 'Widget C', quantity: 300, unitPrice: 12.75, total: 3825, status: 'Draft', createdAt: '2026-05-02', region: 'North America' },
{ id: '7', orderCode: 'ORD-2026-007', customer: 'Cyberdyne', email: 'info@cyberdyne.jp', product: 'Gadget Pro', quantity: 150, unitPrice: 15.0, total: 2250, status: 'Cancelled', createdAt: '2026-05-15', region: 'Asia Pacific' },
{ id: '8', orderCode: 'ORD-2026-008', customer: 'Oscorp', email: 'orders@oscorp.com', product: 'Widget B', quantity: 90, unitPrice: 89.99, total: 8099.1, status: 'Shipped', createdAt: '2026-06-01', region: 'North America' },
{ id: '9', orderCode: 'ORD-2026-009', customer: 'LexCorp', email: 'lex@lexcorp.com', product: 'Gadget Elite', quantity: 5, unitPrice: 499.0, total: 2495, status: 'Processing', createdAt: '2026-06-12', region: 'Europe' },
{ id: '10', orderCode: 'ORD-2026-010', customer: 'Pied Piper', email: 'richard@piedpiper.io', product: 'Widget A', quantity: 500, unitPrice: 24.5, total: 12250, status: 'Pending', createdAt: '2026-06-20', region: 'North America' },
{ id: '11', orderCode: 'ORD-2026-011', customer: 'Hooli', email: 'gavin@hooli.com', product: 'Gadget Pro', quantity: 1000, unitPrice: 15.0, total: 15000, status: 'Delivered', createdAt: '2026-07-01', region: 'North America' },
{ id: '12', orderCode: 'ORD-2026-012', customer: 'Massive Dynamic', email: 'nina@massive.com', product: 'Widget C', quantity: 60, unitPrice: 12.75, total: 765, status: 'Shipped', createdAt: '2026-07-08', region: 'Europe' },
];
/* ─── Status Badge Renderer ─────────────────────────────────────── */
const STATUS_COLORS: Record<OrderRow['status'], string> = {
Draft: 'gray',
Pending: 'warning',
Processing: 'info',
Shipped: 'brand',
Delivered: 'success',
Cancelled: 'error',
};
function StatusCellRenderer({ value }: { value: OrderRow['status'] }) {
return (
<Badge size="sm" variant="light" color={STATUS_COLORS[value] || 'gray'}>
{value}
</Badge>
);
}
/* ─── Currency Formatter ────────────────────────────────────────── */
function currencyFormatter(params: ValueFormatterParams): string {
if (params.value == null) return '-';
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
}).format(params.value);
}
/* ─── Showcase Component ────────────────────────────────────────── */
export default function AgGridShowcase() {
const [gridHeight, setGridHeight] = useState(460);
const [rowModelType, setRowModelType] = useState<string>('clientSide');
const defaultColDef = useMemo<ColDef>(
() => ({
flex: 1,
minWidth: 100,
sortable: true,
filter: true,
resizable: true,
}),
[],
);
const columnDefs = useMemo<ColDef<OrderRow>[]>(
() => [
{
field: 'orderCode',
headerName: 'Order Code',
pinned: 'left',
minWidth: 140,
filter: 'agTextColumnFilter',
},
{
field: 'status',
headerName: 'Status',
minWidth: 120,
cellRenderer: StatusCellRenderer,
filter: 'agSetColumnFilter',
},
{
field: 'customer',
headerName: 'Customer',
minWidth: 160,
filter: 'agTextColumnFilter',
},
{
field: 'email',
headerName: 'Email',
minWidth: 180,
filter: 'agTextColumnFilter',
},
{
field: 'product',
headerName: 'Product',
minWidth: 130,
filter: 'agSetColumnFilter',
},
{
field: 'region',
headerName: 'Region',
minWidth: 140,
filter: 'agSetColumnFilter',
enableRowGroup: true,
},
{
field: 'quantity',
headerName: 'Qty',
minWidth: 80,
filter: 'agNumberColumnFilter',
type: 'numericColumn',
},
{
field: 'unitPrice',
headerName: 'Unit Price',
minWidth: 110,
filter: 'agNumberColumnFilter',
type: 'numericColumn',
valueFormatter: currencyFormatter,
},
{
field: 'total',
headerName: 'Total',
minWidth: 120,
filter: 'agNumberColumnFilter',
type: 'numericColumn',
valueFormatter: currencyFormatter,
},
{
field: 'createdAt',
headerName: 'Created Date',
minWidth: 130,
filter: 'agDateColumnFilter',
},
],
[],
);
return (
<AgGridProvider bypassLicense>
<Stack gap="xl">
{/* ── Info Card ────────────────────────────────── */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="xs">
AG Grid Enterprise Mantine Integration
</Title>
<Text size="sm" c="dimmed" mb="md">
This showcase demonstrates AG Grid integrated with Mantine's theme system. The grid
automatically adapts to dark/light mode changes, inherits brand colors, typography, and
border radius from the Mantine theme — all via AG Grid's built-in CSS variable system with
zero custom stylesheets. Toggle the color scheme in the Theme Controls above to see it in
action.
</Text>
<Group>
<NumberInput
label="Grid Height (px)"
value={gridHeight}
onChange={(val) => setGridHeight(Number(val) || 400)}
min={300}
max={800}
step={50}
w={180}
/>
<Select
label="Row Model"
value={rowModelType}
onChange={(val) => setRowModelType(val || 'clientSide')}
data={[
{ value: 'clientSide', label: 'Client Side' },
{ value: 'serverSide', label: 'Server Side (no datasource)' },
]}
w={260}
/>
</Group>
</Card>
{/* ── AG Grid ──────────────────────────────────── */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">
Orders Data Grid
</Title>
<div style={{ height: gridHeight }}>
<DataGrid<OrderRow>
rowData={SAMPLE_DATA}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
rowSelection={{ mode: 'multiRow', checkboxes: true }}
pagination
paginationPageSize={10}
animateRows
enableCellTextSelection
suppressCopyRowsToClipboard
/>
</div>
</Card>
</Stack>
</AgGridProvider>
);
}
+9 -1
View File
@@ -1,5 +1,5 @@
import { lazy } from 'react';
import { Layers } from 'lucide-react';
import { Layers, Table2 } from 'lucide-react';
export const COMPONENTS_REGISTRY = {
actionTools: {
@@ -9,4 +9,12 @@ export const COMPONENTS_REGISTRY = {
icon: Layers,
component: lazy(() => import('./components/ActionToolsShowcase')),
},
agGrid: {
id: 'ag-grid',
name: 'AG Grid',
description: 'Enterprise Data Grid with Mantine Theme Integration',
icon: Table2,
component: lazy(() => import('./components/AgGridShowcase')),
},
};