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
25 changes: 22 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ This is a dual-purpose repository containing both the **Mantine DataTable** comp
## Project Architecture

### Dual Repository Structure

- **Package code**: `package/` - The actual DataTable component exported to npm
- **Documentation site**: `app/`, `components/` - Next.js app with examples and docs
- **Build outputs**: Package builds to `dist/`, docs build for GitHub Pages deployment

### Package Development Flow

```bash
# Core development commands (use pnpm, not yarn despite legacy docs)
pnpm dev # Start Next.js dev server for docs/examples
Expand All @@ -20,6 +22,7 @@ pnpm lint # ESLint + TypeScript checks
```

### Component Architecture Pattern

The DataTable follows a **composition-based architecture** with specialized sub-components:

```typescript
Expand All @@ -39,29 +42,36 @@ Each sub-component has its own `.tsx`, `.css`, and sometimes `.module.css` files
## Development Conventions

### Import Alias Pattern

Examples use `import { DataTable } from '__PACKAGE__'` - this resolves to the local package during development. Never import from `mantine-datatable` in examples.

### TypeScript Patterns

- **Generic constraints**: `DataTable<T>` where T extends record type
- **Prop composition**: Props inherit from base Mantine components (TableProps, etc.)
- **Accessor pattern**: Use `idAccessor` prop for custom ID fields, defaults to `'id'`

### CSS Architecture

- **Layered imports**: `styles.css` imports all component styles
- **CSS layers**: `@layer mantine, mantine-datatable` for proper specificity
- **Utility classes**: Defined in `utilityClasses.css` for common patterns
- **CSS variables**: Dynamic values injected via `cssVariables.ts`

### Hook Patterns

Custom hooks follow the pattern `useDataTable*` and are located in `package/hooks/`:

- `useDataTableColumns` - Column management and persistence
- `useRowExpansion` - Row expansion state
- `useLastSelectionChangeIndex` - Selection behavior

## Documentation Development

### Example Structure

Each example in `app/examples/` follows this pattern:

```
feature-name/
├── page.tsx # Next.js page with controls
Expand All @@ -70,50 +80,59 @@ feature-name/
```

### Code Block Convention

Use the `CodeBlock` component for syntax highlighting. Example files should be minimal and focused on demonstrating a single feature clearly.

## Data Patterns

### Record Structure

Examples use consistent data shapes:

- `companies.json` - Basic company data with id, name, address
- `employees.json` - Employee data with departments/relationships
- `employees.json` - Employee data with departments/relationships
- `async.ts` - Simulated API calls with delay/error simulation

### Selection Patterns

- **Gmail-style additive selection**: Shift+click for range selection
- **Trigger modes**: `'checkbox'` | `'row'` | `'cell'`
- **Custom selection logic**: Use `isRecordSelectable` for conditional selection

## Build System

### Package Build (tsup)

- **ESM**: `tsup.esm.ts` - Modern module format
- **CJS**: `tsup.cjs.ts` - CommonJS compatibility
- **CJS**: `tsup.cjs.ts` - CommonJS compatibility
- **Types**: `tsup.dts.ts` - TypeScript declarations
- **CSS**: PostCSS processes styles to `dist/`

### Documentation Deployment

- **GitHub Pages**: `output: 'export'` in `next.config.js`
- **Base path**: `/mantine-datatable` when `GITHUB_PAGES=true`
- **Environment injection**: Package version, NPM downloads via build-time fetch

## Common Patterns

### Adding New Features

1. Create component in `package/` with `.tsx` and `.css` files
2. Add to main `DataTable.tsx` component composition
3. Export new types from `package/types/index.ts`
4. Create example in `app/examples/new-feature/`
5. Update main navigation in `app/config.ts`

### Styling New Components

- Use CSS custom properties for theming
- Follow existing naming: `.mantine-datatable-component-name`
- Import CSS in `package/styles.css`
- Add utility classes to `utilityClasses.css` if reusable

### TypeScript Integration

- Extend base Mantine props where possible
- Use composition over inheritance for prop types
- Export all public types from `package/types/index.ts`
Expand All @@ -124,4 +143,4 @@ Examples use consistent data shapes:
- **Virtualization**: Not implemented - DataTable handles reasonable record counts (< 1000s)
- **Memoization**: Use `useMemo` for expensive column calculations
- **CSS-in-JS**: Avoided in favor of CSS modules for better performance
- **Bundle size**: Keep dependencies minimal (only Mantine + React)
- **Bundle size**: Keep dependencies minimal (only Mantine + React)
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/inline-cell-editing',
title: 'Inline cell editing',
description: `Example: inline cell editing with ${PRODUCT_NAME}`,
},
{
href: '/examples/using-with-mantine-contextmenu',
title: `Using with ${MANTINE_CONTEXTMENU_PRODUCT_NAME}`,
Expand Down
64 changes: 64 additions & 0 deletions app/examples/inline-cell-editing/InlineCellEditingExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import { DataTable } from '__PACKAGE__';
import { useMemo, useState } from 'react';
import companies from '~/data/companies.json';

type Company = {
id: string;
name: string;
streetAddress: string;
city: string;
state: string;
employees: number;
foundedDate: string;
};

export function InlineCellEditingExample() {
const initialRecords = useMemo<Company[]>(
() =>
companies.slice(0, 5).map((company, index) => ({
...company,
employees: 100 + index * 50,
foundedDate: new Date(2015 + index, index % 12, 1).toISOString(),
})),
[]
);

const [data, setData] = useState(initialRecords);

const handleEdit = (record: Company, index: number) => {
const newData = [...data];
newData[index] = record;
setData(newData);
};

return (
<DataTable
columns={[
{
accessor: 'name',
editable: true,
editType: 'text',
onEdit: handleEdit,
},
{
accessor: 'employees',
editable: true,
editType: 'number',
onEdit: handleEdit,
},
{
accessor: 'foundedDate',
title: 'Founded',
editable: true,
editType: 'date',
onEdit: handleEdit,
},
{ accessor: 'city' },
{ accessor: 'state' },
]}
records={data}
/>
);
}
46 changes: 46 additions & 0 deletions app/examples/inline-cell-editing/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Route } from 'next';
import { PRODUCT_NAME } from '~/app/config';
import { CodeBlock } from '~/components/CodeBlock';
import { PageNavigation } from '~/components/PageNavigation';
import { PageTitle } from '~/components/PageTitle';
import { Txt } from '~/components/Txt';
import { readCodeFile } from '~/lib/code';
import { allPromiseProps, getRouteMetadata } from '~/lib/utils';
import { InlineCellEditingExample } from './InlineCellEditingExample';

const PATH: Route = '/examples/inline-cell-editing';

export const metadata = getRouteMetadata(PATH);

export default async function InlineEditingExamplePage() {
const code = await allPromiseProps({
'InlineCellEditingExample.tsx': readCodeFile<string>(`${PATH}/InlineCellEditingExample.tsx`),
'companies.json': readCodeFile<string>('/../data/companies.json'),
});

return (
<>
<PageTitle of={PATH} />
<Txt>
This example demonstrates how to implement inline cell editing in {PRODUCT_NAME}. This is achieved by setting
the <code>editable</code> property to <code>true</code> in the column definition. Additionally, the{' '}
<code>onEdit</code> callback is provided to handle updates to the record when the cell value is changed.
</Txt>
<Txt>
The <code>editType</code> property allows you to specify the type of input to use when editing cells. Supported
types are: <code>text</code> (default), <code>number</code>, <code>date</code>, and <code>boolean</code>. The
DataTable automatically renders the appropriate input component for each type - TextInput for text, NumberInput
for numbers, DatePickerInput for dates, and Checkbox for booleans.
</Txt>
<Txt>
This is baked in to the <code>DataTable</code> component for the column definitions, so no additional libraries
are required. However, this only supports the basic single cell editing scenario, for a more complex case of
editing the entire row or adding validation it is still recommended to implement the logic yourself by changing
the logic of the <code>render</code> function of the column to show input fields when in edit mode.
</Txt>
<CodeBlock tabs={{ code, keys: ['InlineCellEditingExample.tsx', 'companies.json'] }} />
<InlineCellEditingExample />
<PageNavigation of={PATH} />
</>
);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
},
"peerDependencies": {
"@mantine/core": ">=8.3",
"@mantine/dates": ">=8.3",
"@mantine/hooks": ">=8.3",
"clsx": ">=2",
"react": ">=19",
Expand Down
6 changes: 6 additions & 0 deletions package/DataTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ export function DataTableRow<T>({
cellsClassName,
cellsStyle,
customCellAttributes,
editable,
onEdit,
editType,
} = { ...defaultColumnProps, ...columnProps };

return (
Expand Down Expand Up @@ -148,6 +151,9 @@ export function DataTableRow<T>({
render={render}
defaultRender={defaultColumnRender}
customCellAttributes={customCellAttributes}
editable={editable}
onEdit={onEdit}
editType={editType}
/>
);
})}
Expand Down
Loading