Skip to content

Commit 14560c2

Browse files
authored
Merge pull request #782 from icflorescu/next
Add full RTL support, update deps, bump version to release 9.3.9
2 parents f52f130 + a96d0cc commit 14560c2

17 files changed

+2709
-1561
lines changed

.github/copilot-instructions.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ This is a dual-purpose repository containing both the **Mantine DataTable** comp
55
## Project Architecture
66

77
### Dual Repository Structure
8+
89
- **Package code**: `package/` - The actual DataTable component exported to npm
910
- **Documentation site**: `app/`, `components/` - Next.js app with examples and docs
1011
- **Build outputs**: Package builds to `dist/`, docs build for GitHub Pages deployment
1112

1213
### Package Development Flow
14+
1315
```bash
1416
# Core development commands (use pnpm, not yarn despite legacy docs)
1517
pnpm dev # Start Next.js dev server for docs/examples
@@ -20,6 +22,7 @@ pnpm lint # ESLint + TypeScript checks
2022
```
2123

2224
### Component Architecture Pattern
25+
2326
The DataTable follows a **composition-based architecture** with specialized sub-components:
2427

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

4144
### Import Alias Pattern
45+
4246
Examples use `import { DataTable } from '__PACKAGE__'` - this resolves to the local package during development. Never import from `mantine-datatable` in examples.
4347

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

4954
### CSS Architecture
55+
5056
- **Layered imports**: `styles.css` imports all component styles
5157
- **CSS layers**: `@layer mantine, mantine-datatable` for proper specificity
5258
- **Utility classes**: Defined in `utilityClasses.css` for common patterns
5359
- **CSS variables**: Dynamic values injected via `cssVariables.ts`
5460

5561
### Hook Patterns
62+
5663
Custom hooks follow the pattern `useDataTable*` and are located in `package/hooks/`:
64+
5765
- `useDataTableColumns` - Column management and persistence
5866
- `useRowExpansion` - Row expansion state
5967
- `useLastSelectionChangeIndex` - Selection behavior
6068

6169
## Documentation Development
6270

6371
### Example Structure
72+
6473
Each example in `app/examples/` follows this pattern:
74+
6575
```
6676
feature-name/
6777
├── page.tsx # Next.js page with controls
@@ -70,50 +80,59 @@ feature-name/
7080
```
7181

7282
### Code Block Convention
83+
7384
Use the `CodeBlock` component for syntax highlighting. Example files should be minimal and focused on demonstrating a single feature clearly.
7485

7586
## Data Patterns
7687

7788
### Record Structure
89+
7890
Examples use consistent data shapes:
91+
7992
- `companies.json` - Basic company data with id, name, address
80-
- `employees.json` - Employee data with departments/relationships
93+
- `employees.json` - Employee data with departments/relationships
8194
- `async.ts` - Simulated API calls with delay/error simulation
8295

8396
### Selection Patterns
97+
8498
- **Gmail-style additive selection**: Shift+click for range selection
8599
- **Trigger modes**: `'checkbox'` | `'row'` | `'cell'`
86100
- **Custom selection logic**: Use `isRecordSelectable` for conditional selection
87101

88102
## Build System
89103

90104
### Package Build (tsup)
105+
91106
- **ESM**: `tsup.esm.ts` - Modern module format
92-
- **CJS**: `tsup.cjs.ts` - CommonJS compatibility
107+
- **CJS**: `tsup.cjs.ts` - CommonJS compatibility
93108
- **Types**: `tsup.dts.ts` - TypeScript declarations
94109
- **CSS**: PostCSS processes styles to `dist/`
95110

96111
### Documentation Deployment
112+
97113
- **GitHub Pages**: `output: 'export'` in `next.config.js`
98114
- **Base path**: `/mantine-datatable` when `GITHUB_PAGES=true`
99115
- **Environment injection**: Package version, NPM downloads via build-time fetch
100116

101117
## Common Patterns
102118

103119
### Adding New Features
120+
104121
1. Create component in `package/` with `.tsx` and `.css` files
105122
2. Add to main `DataTable.tsx` component composition
106123
3. Export new types from `package/types/index.ts`
107124
4. Create example in `app/examples/new-feature/`
108125
5. Update main navigation in `app/config.ts`
109126

110127
### Styling New Components
128+
111129
- Use CSS custom properties for theming
112130
- Follow existing naming: `.mantine-datatable-component-name`
113131
- Import CSS in `package/styles.css`
114132
- Add utility classes to `utilityClasses.css` if reusable
115133

116134
### TypeScript Integration
135+
117136
- Extend base Mantine props where possible
118137
- Use composition over inheritance for prop types
119138
- Export all public types from `package/types/index.ts`
@@ -124,4 +143,4 @@ Examples use consistent data shapes:
124143
- **Virtualization**: Not implemented - DataTable handles reasonable record counts (< 1000s)
125144
- **Memoization**: Use `useMemo` for expensive column calculations
126145
- **CSS-in-JS**: Avoided in favor of CSS modules for better performance
127-
- **Bundle size**: Keep dependencies minimal (only Mantine + React)
146+
- **Bundle size**: Keep dependencies minimal (only Mantine + React)

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
The following is a list of notable changes to the Mantine DataTable component.
44
Minor versions that are not listed in the changelog are bug fixes and small improvements.
55

6+
## 8.3.9 (2025-12-05)
7+
8+
- Implement comprehensive RTL support, thanks to [Reem Assaf](https://github.com/ReemX) (see [PR #781](https://github.com/icflorescu/mantine-datatable/pull/781/))
9+
- Update deps to ensure compatibility with Mantine 8.3.9
10+
611
## 8.3.8 (2025-11-17)
712

813
- Fix [#758](https://github.com/icflorescu/mantine-datatable/discussions/758) - implement `getPaginationItemProps`

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ The [lightweight](https://bundlephobia.com/package/mantine-datatable), dependenc
3434
- **[AutoAnimate support](https://icflorescu.github.io/mantine-datatable/examples/using-with-auto-animate)** - animate row sorting, addition and removal
3535
- **[Column reordering, toggling](https://icflorescu.github.io/mantine-datatable/examples/column-dragging-and-toggling)** and **[resizing](https://icflorescu.github.io/mantine-datatable/examples/column-resizing)** - thanks to the outstanding work of [Giovambattista Fazioli](https://github.com/gfazioli)
3636
- **[Drag-and-drop support](https://icflorescu.github.io/mantine-datatable/examples/row-dragging)** - implemented using [@hello-pangea/dnd](https://github.com/hello-pangea/dnd) thanks to the outstanding work of [Mohd Ahmad](https://github.com/MohdAhmad1)
37+
- **[Comprehensive RTL support](https://icflorescu.github.io/mantine-datatable/examples/rtl-support)** - the context menu adjusts automatically to [Mantine `DirectionProvider`](https://mantine.dev/styles/rtl/)'s settings, thanks to the outstanding work of [Reem Assaf](https://github.com/ReemX)
3738
- **More** - check out the [full documentation](https://icflorescu.github.io/mantine-datatable/)
3839

3940
## Trusted by thousands of developers and companies from around the world

app/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ export const ROUTES: RouteInfo[] = [
222222
title: 'Pinning the first column',
223223
description: `Example: how to pin the first column on ${PRODUCT_NAME}`,
224224
},
225+
{
226+
href: '/examples/rtl-support',
227+
title: 'RTL support',
228+
description: `Example: demonstrating RTL (Right-to-Left) support in ${PRODUCT_NAME} for languages like Arabic, Hebrew, and Persian`,
229+
},
225230
{
226231
href: '/examples/links-or-buttons-inside-clickable-rows-or-cells',
227232
title: 'Links or buttons inside clickable rows or cells',
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
'use client';
2+
3+
import { ActionIcon, Box, Button, DirectionProvider, Group, SegmentedControl, Stack, Text } from '@mantine/core';
4+
import { IconColumns3, IconEdit, IconEye, IconTrash } from '@tabler/icons-react';
5+
import { DataTable, useDataTableColumns, type DataTableSortStatus } from '__PACKAGE__';
6+
import { useMemo, useState } from 'react';
7+
import { companies, employees, type Employee } from '~/data';
8+
9+
// Example 1: Basic table without pinned columns
10+
export function RTLBasicExample({ direction }: { direction: 'ltr' | 'rtl' }) {
11+
return (
12+
<DirectionProvider key={`basic-${direction}`} initialDirection={direction} detectDirection={false}>
13+
<Box dir={direction}>
14+
<DataTable
15+
withTableBorder
16+
withColumnBorders
17+
records={companies.slice(0, 5)}
18+
columns={[
19+
{ accessor: 'name', width: 200 },
20+
{ accessor: 'streetAddress', width: 200 },
21+
{ accessor: 'city', width: 150 },
22+
{ accessor: 'state', width: 100 },
23+
]}
24+
/>
25+
</Box>
26+
</DirectionProvider>
27+
);
28+
}
29+
30+
// Example 2: With column dragging
31+
export function RTLDraggingExample({ direction }: { direction: 'ltr' | 'rtl' }) {
32+
const key = 'rtl-dragging-example';
33+
34+
const { effectiveColumns, resetColumnsOrder } = useDataTableColumns({
35+
key,
36+
columns: [
37+
{ accessor: 'name', width: 200, draggable: true },
38+
{ accessor: 'streetAddress', width: 250, draggable: true },
39+
{ accessor: 'city', width: 150, draggable: true },
40+
{ accessor: 'state', width: 100 },
41+
],
42+
});
43+
44+
return (
45+
<DirectionProvider key={`dragging-${direction}`} initialDirection={direction} detectDirection={false}>
46+
<Box dir={direction}>
47+
<Stack gap="xs">
48+
<Group gap="xs">
49+
<Button size="xs" variant="light" leftSection={<IconColumns3 size={16} />} onClick={resetColumnsOrder}>
50+
Reset order
51+
</Button>
52+
</Group>
53+
<DataTable
54+
withTableBorder
55+
withColumnBorders
56+
storeColumnsKey={key}
57+
records={companies.slice(0, 8)}
58+
columns={effectiveColumns}
59+
/>
60+
</Stack>
61+
</Box>
62+
</DirectionProvider>
63+
);
64+
}
65+
66+
// Example 3: With pinned columns, selection, sorting, pagination, and resizing
67+
export function RTLFullFeaturedExample({ direction }: { direction: 'ltr' | 'rtl' }) {
68+
const PAGE_SIZES = [5, 10, 20];
69+
const [page, setPage] = useState(1);
70+
const [recordsPerPage, setRecordsPerPage] = useState(PAGE_SIZES[0]);
71+
const [selectedRecords, setSelectedRecords] = useState<Employee[]>([]);
72+
const [sortStatus, setSortStatus] = useState<DataTableSortStatus<Employee>>({
73+
columnAccessor: 'firstName',
74+
direction: 'asc',
75+
});
76+
77+
const sortedRecords = useMemo(() => {
78+
const sorted = [...employees].sort((a, b) => {
79+
const accessor = sortStatus.columnAccessor as keyof Employee;
80+
const aValue = a[accessor];
81+
const bValue = b[accessor];
82+
if (typeof aValue === 'string' && typeof bValue === 'string') {
83+
return sortStatus.direction === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
84+
}
85+
return 0;
86+
});
87+
return sorted;
88+
}, [sortStatus]);
89+
90+
const paginatedRecords = useMemo(() => {
91+
const start = (page - 1) * recordsPerPage;
92+
return sortedRecords.slice(start, start + recordsPerPage);
93+
}, [sortedRecords, page, recordsPerPage]);
94+
95+
return (
96+
<DirectionProvider key={`full-${direction}`} initialDirection={direction} detectDirection={false}>
97+
<Box dir={direction}>
98+
<DataTable
99+
withTableBorder
100+
withColumnBorders
101+
striped
102+
highlightOnHover
103+
pinFirstColumn
104+
pinLastColumn
105+
minHeight={300}
106+
columns={[
107+
{
108+
accessor: 'firstName',
109+
title: 'First Name',
110+
sortable: true,
111+
resizable: true,
112+
width: 120,
113+
},
114+
{
115+
accessor: 'lastName',
116+
title: 'Last Name',
117+
sortable: true,
118+
resizable: true,
119+
width: 120,
120+
},
121+
{
122+
accessor: 'email',
123+
sortable: true,
124+
resizable: true,
125+
width: 220,
126+
},
127+
{
128+
accessor: 'department.name',
129+
title: 'Department',
130+
resizable: true,
131+
width: 150,
132+
},
133+
{
134+
accessor: 'department.company.name',
135+
title: 'Company',
136+
resizable: true,
137+
width: 180,
138+
},
139+
{
140+
accessor: 'department.company.city',
141+
title: 'City',
142+
resizable: true,
143+
width: 120,
144+
},
145+
{
146+
accessor: 'actions',
147+
title: 'Actions',
148+
width: 100,
149+
render: () => (
150+
<Group gap={4} justify="center" wrap="nowrap">
151+
<ActionIcon size="sm" variant="subtle" color="blue">
152+
<IconEye size={16} />
153+
</ActionIcon>
154+
<ActionIcon size="sm" variant="subtle" color="green">
155+
<IconEdit size={16} />
156+
</ActionIcon>
157+
<ActionIcon size="sm" variant="subtle" color="red">
158+
<IconTrash size={16} />
159+
</ActionIcon>
160+
</Group>
161+
),
162+
},
163+
]}
164+
records={paginatedRecords}
165+
totalRecords={employees.length}
166+
recordsPerPage={recordsPerPage}
167+
onRecordsPerPageChange={setRecordsPerPage}
168+
recordsPerPageOptions={PAGE_SIZES}
169+
page={page}
170+
onPageChange={setPage}
171+
sortStatus={sortStatus}
172+
onSortStatusChange={setSortStatus}
173+
selectedRecords={selectedRecords}
174+
onSelectedRecordsChange={setSelectedRecords}
175+
/>
176+
</Box>
177+
</DirectionProvider>
178+
);
179+
}
180+
181+
// Main example component with direction toggle
182+
export function RTLSupportExample() {
183+
const [direction, setDirection] = useState<'ltr' | 'rtl'>('ltr');
184+
185+
return (
186+
<Stack gap="xl">
187+
<Group justify="center">
188+
<Text size="sm" fw={700}>
189+
Direction:
190+
</Text>
191+
<SegmentedControl
192+
value={direction}
193+
onChange={(value) => setDirection(value as 'ltr' | 'rtl')}
194+
data={[
195+
{ label: 'LTR (Left to Right)', value: 'ltr' },
196+
{ label: 'RTL (Right to Left)', value: 'rtl' },
197+
]}
198+
/>
199+
</Group>
200+
201+
<Stack gap="md">
202+
<Text fw={700}>Basic table:</Text>
203+
<RTLBasicExample direction={direction} />
204+
</Stack>
205+
206+
<Stack gap="md">
207+
<Text fw={700}>Column dragging:</Text>
208+
<RTLDraggingExample direction={direction} />
209+
</Stack>
210+
211+
<Stack gap="md">
212+
<Text fw={700}>Full-featured: pinned columns, selection, sorting, pagination, resizing:</Text>
213+
<RTLFullFeaturedExample direction={direction} />
214+
</Stack>
215+
</Stack>
216+
);
217+
}

0 commit comments

Comments
 (0)