Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ export const ROUTES: RouteInfo[] = [
title: 'Handling cell clicks',
description: `Example: handling cell click events on ${PRODUCT_NAME}`,
},
{
href: '/examples/editing-cells',
title: 'Editing cells',
description: `Example: how to implement inline cell editing with ${PRODUCT_NAME}`,
},
{
href: '/examples/using-with-mantine-contextmenu',
title: `Using with ${MANTINE_CONTEXTMENU_PRODUCT_NAME}`,
Expand Down
50 changes: 50 additions & 0 deletions app/examples/editing-cells/EditingCellsCallbackExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client';

import { DataTable } from '__PACKAGE__';
import { Code, Stack, Text } from '@mantine/core';
import { useState } from 'react';
import { type Company, companies } from '~/data';

const initialRecords = companies.slice(0, 3);

type EditEvent = { accessor: string; value: string; recordId: number };

export function EditingCellsCallbackExample() {
const [records, setRecords] = useState<Company[]>(initialRecords);
const [log, setLog] = useState<EditEvent[]>([]);

return (
// example-start
<Stack>
<DataTable
withTableBorder
withColumnBorders
columns={[
{ accessor: 'name', editable: true },
{ accessor: 'city', editable: true },
{ accessor: 'state', editable: true },
]}
records={records}
onCellEdit={({ record, accessor, value }) => {
setRecords((current) =>
current.map((r) => (r.id === record.id ? { ...r, [accessor as keyof Company]: value } : r))
);
setLog((current) => [{ accessor: String(accessor), value, recordId: record.id }, ...current].slice(0, 5));
}}
/>
{log.length > 0 && (
<Stack gap={4}>
<Text size="xs" c="dimmed">
Last commits (newest first):
</Text>
{log.map((entry, i) => (
<Code key={i} block>
{`{ accessor: "${entry.accessor}", value: "${entry.value}", record.id: ${entry.recordId} }`}
</Code>
))}
</Stack>
)}
</Stack>
// example-end
);
}
104 changes: 104 additions & 0 deletions app/examples/editing-cells/EditingCellsCustomRenderExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use client';

import { DataTable } from '__PACKAGE__';
import { Select } from '@mantine/core';
import { useState } from 'react';
import { type Company, companies } from '~/data';

const initialRecords = companies.slice(0, 5);

const US_STATES = [
'AK',
'AL',
'AR',
'AZ',
'CA',
'CO',
'CT',
'DC',
'DE',
'FL',
'GA',
'HI',
'IA',
'ID',
'IL',
'IN',
'KS',
'KY',
'LA',
'MA',
'MD',
'ME',
'MI',
'MN',
'MO',
'MS',
'MT',
'NC',
'ND',
'NE',
'NH',
'NJ',
'NM',
'NV',
'NY',
'OH',
'OK',
'OR',
'PA',
'RI',
'SC',
'SD',
'TN',
'TX',
'UT',
'VA',
'VT',
'WA',
'WI',
'WV',
'WY',
];

export function EditingCellsCustomRenderExample() {
const [records, setRecords] = useState<Company[]>(initialRecords);

return (
// example-start
<DataTable
withTableBorder
withColumnBorders
columns={[
{ accessor: 'name', editable: true },
{ accessor: 'city', editable: true },
{
accessor: 'state',
editable: true,
editRender: (_record, _index, { value, onCommit, onCancel }) => (
<Select
autoFocus
variant="unstyled"
data={US_STATES}
value={value}
onChange={(val) => {
if (val) onCommit(val);
}}
onKeyDown={(e) => {
if (e.key === 'Escape') onCancel();
}}
styles={{ input: { padding: 0, minHeight: 'unset', height: 'auto' } }}
/>
),
},
]}
records={records}
onCellEdit={({ record, accessor, value }) => {
setRecords((current) =>
current.map((r) => (r.id === record.id ? { ...r, [accessor as keyof Company]: value } : r))
);
}}
/>
// example-end
);
}
32 changes: 32 additions & 0 deletions app/examples/editing-cells/EditingCellsExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import { DataTable } from '__PACKAGE__';
import { useState } from 'react';
import { type Company, companies } from '~/data';

const initialRecords = companies.slice(0, 5);

export function EditingCellsExample() {
const [records, setRecords] = useState<Company[]>(initialRecords);

return (
// example-start
<DataTable
withTableBorder
withColumnBorders
columns={[
{ accessor: 'name', editable: true },
{ accessor: 'streetAddress' },
{ accessor: 'city', editable: true },
{ accessor: 'state', editable: true },
]}
records={records}
onCellEdit={({ record, accessor, value }) => {
setRecords((current) =>
current.map((r) => (r.id === record.id ? { ...r, [accessor as keyof Company]: value } : r))
);
}}
/>
// example-end
);
}
46 changes: 46 additions & 0 deletions app/examples/editing-cells/EditingCellsGlobalModeExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

import { DataTable } from '__PACKAGE__';
import { Button, Group } from '@mantine/core';
import { useState } from 'react';
import { type Company, companies } from '~/data';

const initialRecords = companies.slice(0, 5);

export function EditingCellsGlobalModeExample() {
const [records, setRecords] = useState<Company[]>(initialRecords);
const [editMode, setEditMode] = useState<'cell' | 'global'>('cell');

return (
// example-start
<div>
<Group mb="sm">
<Button
variant={editMode === 'global' ? 'filled' : 'outline'}
onClick={() => setEditMode((m) => (m === 'global' ? 'cell' : 'global'))}
>
{editMode === 'global' ? 'Exit edit mode' : 'Edit all'}
</Button>
</Group>
<DataTable
withTableBorder
withColumnBorders
editMode={editMode}
editingCellStyle={{ boxShadow: 'inset 0 0 0 2px var(--mantine-color-blue-5)' }}
columns={[
{ accessor: 'name', editable: true },
{ accessor: 'streetAddress' },
{ accessor: 'city', editable: true },
{ accessor: 'state', editable: true },
]}
records={records}
onCellEdit={({ record, accessor, value }) => {
setRecords((current) =>
current.map((r) => (r.id === record.id ? { ...r, [accessor as keyof Company]: value } : r))
);
}}
/>
</div>
// example-end
);
}
76 changes: 76 additions & 0 deletions app/examples/editing-cells/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Code } from '@mantine/core';
import type { Route } from 'next';
import { CodeBlock } from '~/components/CodeBlock';
import { PageNavigation } from '~/components/PageNavigation';
import { PageSubtitle } from '~/components/PageSubtitle';
import { PageTitle } from '~/components/PageTitle';
import { Txt } from '~/components/Txt';
import { readCodeFile } from '~/lib/code';
import { allPromiseProps, getRouteMetadata } from '~/lib/utils';
import { EditingCellsCallbackExample } from './EditingCellsCallbackExample';
import { EditingCellsCustomRenderExample } from './EditingCellsCustomRenderExample';
import { EditingCellsExample } from './EditingCellsExample';
import { EditingCellsGlobalModeExample } from './EditingCellsGlobalModeExample';

const PATH: Route = '/examples/editing-cells';

export const metadata = getRouteMetadata(PATH);

export default async function EditingCellsExamplePage() {
const code = await allPromiseProps({
'EditingCellsExample.tsx': readCodeFile<string>(`${PATH}/EditingCellsExample.tsx`),
'EditingCellsCustomRenderExample.tsx': readCodeFile<string>(`${PATH}/EditingCellsCustomRenderExample.tsx`),
'EditingCellsGlobalModeExample.tsx': readCodeFile<string>(`${PATH}/EditingCellsGlobalModeExample.tsx`),
'EditingCellsCallbackExample.tsx': readCodeFile<string>(`${PATH}/EditingCellsCallbackExample.tsx`),
});

return (
<>
<PageTitle of={PATH} />
<Txt>
Mark columns as <Code>editable</Code> and provide an <Code>onCellEdit</Code> handler to enable inline cell
editing. Double-click any editable cell to enter edit mode. Press <Code>Enter</Code> or click away to commit, or
press <Code>Escape</Code> to cancel.
</Txt>
<EditingCellsExample />
<CodeBlock code={code['EditingCellsExample.tsx']} />
<Txt>
Use a function for <Code>editable</Code> to control editability per cell:
</Txt>
<CodeBlock
code={`columns={[
{ accessor: 'name', editable: (record, index) => !record.locked },
]}`}
/>
<Txt>
Control when edits are committed with <Code>commitEditOn</Code> (per column) or <Code>defaultCommitEditOn</Code>{' '}
(table-wide). Default is <Code>['blur', 'enter']</Code>.
</Txt>
<PageSubtitle value="onCellEdit callback" />
<Txt>
The <Code>onCellEdit</Code> callback receives the updated record, its index, the column{' '}
<Code>accessor</Code>, and the new <Code>value</Code>. Double-click a cell below to see it fire.
</Txt>
<EditingCellsCallbackExample />
<CodeBlock code={code['EditingCellsCallbackExample.tsx']} />
<PageSubtitle value="Global edit mode" />
<Txt>
Set <Code>editMode="global"</Code> to put all editable cells into edit mode simultaneously — useful for
spreadsheet-style inline editing. Tab and Shift+Tab navigate between cells. Use <Code>editingCellStyle</Code>{' '}
or <Code>editingCellClassName</Code> to highlight the focused cell.
</Txt>
<EditingCellsGlobalModeExample />
<CodeBlock code={code['EditingCellsGlobalModeExample.tsx']} />
<PageSubtitle value="Custom edit render" />
<Txt>
Provide an <Code>editRender</Code> function on a column to replace the built-in <Code>TextInput</Code> with any
component. Use the <Code>onCommit(value)</Code> override to commit a value in a single call — useful for
controls like <Code>Select</Code> that change and confirm in one event.
</Txt>
<EditingCellsCustomRenderExample />
<CodeBlock code={code['EditingCellsCustomRenderExample.tsx']} />
<Txt>Head over to the next example to discover more features.</Txt>
<PageNavigation of={PATH} />
</>
);
}
Loading