Row selection, keyboard navigation, context menus, column visibility, dual resize modes, and 6 exported hooks — one release, zero compromises.
Introduction
Picture the macOS Finder: you click a file, Shift+click to select a range, right-click for a context menu, double-click a column divider to auto-fit, and hide columns you don't need. Now picture doing all of that in a React table component — with full Mantine integration and zero external dependencies. That's what @gfazioli/mantine-list-view-table 2.0.0 delivers. This isn't an incremental update; it's a rewrite that transforms a sortable table into a complete Finder-style list view, with every piece of logic extracted into reusable, publicly exported hooks.
What's New
✨ Row Selection
Select rows exactly like macOS Finder. Single click selects one row, Cmd/Ctrl+Click toggles, Shift+Click selects a range. Both single and multiple modes are supported, with controlled and uncontrolled state.
<ListViewTable
columns={columns}
data={data}
rowKey="id"
selectionMode="multiple"
onSelectionChange={(keys, records) => setSelected(keys)}
selectedRowColor="blue"
/>
Selected rows get a visible highlight that desaturates when the component loses focus — matching Finder's behavior pixel for pixel.
⌨️ Keyboard Navigation
When selectionMode is set, keyboard navigation activates automatically:
| Key | Action |
|---|---|
| Arrow Up/Down | Move focus between rows |
| Enter | Activate row (onRowActivate) |
| Space | Toggle selection |
| Home / End | Jump to first / last row |
| Cmd/Ctrl+A | Select all (multiple mode) |
<ListViewTable
selectionMode="multiple"
enableKeyboardNavigation
onRowActivate={(record) => openFile(record)}
/>
🖱️ Context Menu
Right-click any row to show a context menu powered by Mantine's Menu component. The clicked row is automatically selected before the menu appears. You get Mantine's full accessibility, keyboard support, and dark mode out of the box.
<ListViewTable
selectionMode="single"
renderContextMenu={({ record }) => (
<>
<Menu.Item leftSection={<IconCopy size={14} />}>Copy</Menu.Item>
<Menu.Item leftSection={<IconDownload size={14} />}>Download</Menu.Item>
<Menu.Divider />
<Menu.Item color="red" leftSection={<IconTrash size={14} />}>Delete</Menu.Item>
</>
)}
/>
👁️ Column Visibility
Hide and show columns programmatically or let users toggle them by right-clicking the table header. Supports both controlled and uncontrolled modes.
<ListViewTable
hiddenColumns={['size', 'modified']}
onHiddenColumnsChange={setHiddenColumns}
enableColumnVisibilityToggle
/>
↔️ Dual Resize Modes
The new resizeMode prop gives you two column resize behaviors:
-
standard(default) — width is traded between the dragged column and its right neighbor. Total table width stays fixed. Great for fixed-width layouts. -
finder— only the dragged column changes width. The table grows freely, just like Finder. Pair withTable.ScrollContainerfor horizontal scrolling.
<ListViewTable enableColumnResizing resizeMode="finder" />
🎯 Double-Click Auto-Fit
Double-click any resize handle to auto-fit the column to its content — measured accurately using off-screen DOM clones. In standard mode, the adjacent column compensates to keep the total width constant.
🪝 6 Exported Hooks
Every piece of internal logic is now a standalone, reusable hook:
| Hook | Purpose |
|---|---|
useSorting |
Sort state with controlled/uncontrolled modes |
useColumnReorder |
Drag-and-drop column reordering |
useColumnResize |
Column resize with standard/finder modes |
useRowSelection |
Row selection (single, multiple, range) |
useKeyboardNavigation |
Arrow/Enter/Space keyboard navigation |
useColumnVisibility |
Column show/hide management |
Import them directly for advanced compositions:
import { useRowSelection, useKeyboardNavigation } from '@gfazioli/mantine-list-view-table';
📋 New Props Summary
| Prop | Type | Default | Description |
|---|---|---|---|
selectionMode |
`'single' \ | 'multiple'` | — |
selectedRows |
React.Key[] |
— | Controlled selected row keys |
defaultSelectedRows |
React.Key[] |
— | Default selected keys (uncontrolled) |
onSelectionChange |
(keys, records) => void |
— | Selection change callback |
selectedRowColor |
MantineColor |
— | Selected row background color |
enableKeyboardNavigation |
boolean |
true when selectionMode is set |
Enable keyboard nav |
onRowActivate |
(record, index) => void |
— | Enter key callback |
renderContextMenu |
(info) => ReactNode |
— | Context menu content (use Menu.Item) |
onRowContextMenu |
(record, index, event) => void |
— | Right-click callback |
hiddenColumns |
string[] |
— | Controlled hidden column keys |
defaultHiddenColumns |
string[] |
— | Default hidden columns (uncontrolled) |
onHiddenColumnsChange |
(keys) => void |
— | Hidden columns change callback |
enableColumnVisibilityToggle |
boolean |
false |
Right-click header to toggle columns |
resizeMode |
`'standard' \ | 'finder'` | 'standard' |
tableProps |
Partial<TableProps> |
— | Props passed to inner <Table>
|
stickyHeader |
boolean |
false |
Sticky table header |
stickyHeaderOffset |
`number \ | string` | 0 |
tabularNums |
boolean |
false |
Tabular number alignment |
🎨 New Styles API
New selectors: selectedRow, focusedRow
New CSS variables:
| Variable | Description |
|---|---|
--list-view-selected-row-color |
Background color of selected rows |
--list-view-sticky-blur |
Backdrop blur for sticky column overlay |
💥 Breaking Changes
[!CAUTION]
This release contains breaking changes. Review the migration guide below before upgrading.
1. Props interface no longer extends TableProps
Table-level props (variant, layout, etc.) must now be passed through tableProps:
// Before
<ListViewTable variant="vertical" />
// After
<ListViewTable tableProps={{ variant: 'vertical' }} />
2. enableColumnReordering and enableColumnResizing default to false
Previously both defaulted to true. Now you must opt in:
<ListViewTable enableColumnReordering enableColumnResizing />
3. Component ref type changed
// Before
const ref = useRef<HTMLTableElement>(null);
// After
const ref = useRef<HTMLDivElement>(null);
4. Context menu requires Menu.Item elements
The renderContextMenu prop must now return Mantine Menu.Item / Menu.Divider components instead of arbitrary JSX:
// Before
renderContextMenu={() => (
<Stack gap={0}>
<UnstyledButton>Copy</UnstyledButton>
</Stack>
)}
// After
renderContextMenu={() => (
<>
<Menu.Item leftSection={<IconCopy size={14} />}>Copy</Menu.Item>
</>
)}
5. visibleMediaQuery removed from column config
Use the new hiddenColumns prop instead for programmatic column visibility.
6. contextMenu Styles API selector removed
The context menu is now powered by Mantine's Menu, which has its own styling system.
🐛 Bug Fixes
-
Double-click no longer triggers drag resize —
event.detail >= 2guard prevents mousedown from starting a drag during double-click - Auto-fit preserves table width — adjacent column compensates the exact delta in standard mode
-
Controlled sort respects external data order — no internal re-sorting when
sortStatus+onSortare both provided - Keyboard navigation bounds check — Enter/Space no longer fire with invalid indices after data filtering
-
Range selection clamped — Shift+click clamps to
[0, data.length - 1]when data changes between clicks
🔧 Improvements
- Resize handle UX overhaul — 14px hit area (20px touch), animated indicator line with spring curve
- Finder-style header focus — inset bottom border instead of background highlight
-
Accessible focus indicator —
:focus-visibleoutline on root element for keyboard users -
Sticky columns without
!important— cleaner CSS specificity -
Vertical variant — now supports dot-notation keys (
getNestedValue) andrenderCell
Migration Guide
Step 1: Install the update
yarn add @gfazioli/mantine-list-view-table@^2.0.0
Step 2: Wrap Table props in tableProps
If you passed Mantine Table props directly, move them:
// Before
<ListViewTable variant="vertical" layout="fixed" />
// After
<ListViewTable tableProps={{ variant: 'vertical', layout: 'fixed' }} />
Step 3: Opt in to reordering/resizing
<ListViewTable enableColumnReordering enableColumnResizing />
Step 4: Update ref type
const ref = useRef<HTMLDivElement>(null); // was HTMLTableElement
Step 5: Update context menu
Replace custom JSX with Menu.Item from @mantine/core:
import { Menu } from '@mantine/core';
renderContextMenu={({ record }) => (
<>
<Menu.Item leftSection={<IconCopy size={14} />}>Copy</Menu.Item>
<Menu.Divider />
<Menu.Item color="red" leftSection={<IconTrash size={14} />}>Delete</Menu.Item>
</>
)}
Step 6: Replace visibleMediaQuery
// Before (on column)
{ key: 'size', visibleMediaQuery: '(min-width: 768px)' }
// After (on component)
<ListViewTable hiddenColumns={isMobile ? ['size'] : []} />
Compatibility
- Mantine: 8.x
- React: 18.x / 19.x
- @tabler/icons-react: ^3.34.0
- License: MIT
Top comments (0)