Skip to content

Commit

Permalink
Merge pull request #469 from icflorescu/next
Browse files Browse the repository at this point in the history
Implement selectionTrigger property
  • Loading branch information
icflorescu authored Nov 10, 2023
2 parents a7f6dfa + 5dcb905 commit 72729a8
Show file tree
Hide file tree
Showing 14 changed files with 218 additions and 98 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
The following is a list of notable changes to the Mantine DataTable component.
Minor versions that are not listed in the changelog are bug fixes and small improvements.

## 7.1.6 (2023-11-10)

- Add `selectionTrigger` property to allow maximizing the selection area to the entire cell holding the checkbox

## 7.1.5 (2023-11-08)

- Fix backgrounds for selection cell and last column when using `pinLastColumn` feature (see issues [#464](https://github.com/icflorescu/mantine-datatable/issues/464) and [#465](https://github.com/icflorescu/mantine-datatable/issues/465))
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ Make sure to browse the comprehensive list of [usage examples](https://icfloresc

## Other useful resources

Mantine DataTable works perfectly with [Mantine Context Menu](https://icflorescu.github.io/mantine-contextmenu/).
Mantine DataTable works perfectly with [Mantine Context Menu](https://icflorescu.github.io/mantine-contextmenu/), a library built by the same author that enables you to enhance your UIs with desktop-grade, lightweight yet fully-featured context menus that respect the Mantine color scheme out of the box:

[![Mantine ContextMenu](https://user-images.githubusercontent.com/581999/279488420-96467a1b-2fb7-4876-bbc0-6976d26ed79b.png)](https://icflorescu.github.io/mantine-contextmenu/)

## Contributing

Expand All @@ -143,11 +145,11 @@ Here's the list of people who have already contributed to Mantine DataTable:

Want to [become a code contributor](https://icflorescu.github.io/mantine-datatable/contribute-and-support)?

## Sponsor the project
## Support the project

If you find this package useful, please consider ❤️ [sponsoring my work](https://github.com/sponsors/icflorescu).
Your sponsorship will help me dedicate more time to maintaining the project and will encourage me to add new features and fix existing bugs.
If you're a company using Mantine DataTable in a commercial project, you can also [hire my services](https://github.com/icflorescu).
If you're a company using Mantine, Mantine DataTable or [Mantine ContextMenu](https://icflorescu.github.io/mantine-contextmenu/) in a commercial project, you can also [hire my services](https://github.com/icflorescu).

## Other means of support

Expand Down
57 changes: 56 additions & 1 deletion app/examples/records-selection/RecordsSelectionExamples.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { Button, Center, Paper, Text } from '@mantine/core';
import { Box, Button, Center, Paper, Stack, Text } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import { IconTrash } from '@tabler/icons-react';
import { DataTable, differenceBy, uniqBy, type DataTableColumn } from '__PACKAGE__';
Expand Down Expand Up @@ -209,3 +209,58 @@ export function RecordsSelectionHorizontalScrollingBehaviorExample() {
);
// example-end
}

export function CellTriggerRecordsSelectionExample() {
const records = companies.slice(0, 3);
const [selectedRecords, setSelectedRecords] = useState<Company[]>([]);

return (
<>
{/* example-start CellTriggerRecordsSelectionExample.tsx */}
<DataTable
// example-skip other props
verticalAlign="top"
striped
highlightOnHover
withTableBorder
withColumnBorders
records={records}
columns={[
{ accessor: 'name' },
{
accessor: 'address',
render: ({ streetAddress, city, state }) => (
<Stack gap={0}>
<Box>{streetAddress}</Box>
<Box>{city}</Box>
<Box>{state}</Box>
</Stack>
),
},
{ accessor: 'missionStatement' },
]}
selectedRecords={selectedRecords}
onSelectedRecordsChange={setSelectedRecords}
// example-resume
selectionTrigger="cell" // 👈 click anywhere in the cell to select the record
/>
{/* example-end */}
<Paper p="md" mt="sm" withBorder>
<Center>
<Button
leftSection={<IconTrash size={16} />}
color="red"
disabled={!selectedRecords.length}
onClick={() => showNotification({ color: 'red', message: 'Deleting data is dangerous!' })}
>
{selectedRecords.length
? `Delete ${
selectedRecords.length === 1 ? 'one selected record' : `${selectedRecords.length} selected records`
}`
: 'Select records to delete'}
</Button>
</Center>
</Paper>
</>
);
}
12 changes: 12 additions & 0 deletions app/examples/records-selection/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { UnorderedList } from '~/components/UnorderedList';
import { readCodeFile } from '~/lib/code';
import { getRouteMetadata } from '~/lib/utils';
import {
CellTriggerRecordsSelectionExample,
CheckboxPropsExample,
DisabledRecordsExample,
RecordsSelectionExample,
Expand All @@ -27,6 +28,7 @@ export default async function RecordsSelectionExamplePage() {
Record<
| 'columns.ts'
| 'RecordsSelectionExample.tsx'
| 'CellTriggerRecordsSelectionExample.tsx'
| 'DisabledRecordsExample.tsx'
| 'CheckboxPropsExample.tsx'
| 'SelectAllRecordsOnAllPagesExample.tsx'
Expand Down Expand Up @@ -126,6 +128,16 @@ export default async function RecordsSelectionExamplePage() {
<RecordsSelectionHorizontalScrollingBehaviorExample />
<Txt>Code:</Txt>
<CodeBlock code={code['RecordsSelectionHorizontalScrollingBehaviorExample.tsx']} />
<PageSubtitle value="Maximizing the selection trigger area to the entire cell" />
<Txt>
By default, selection is triggered when the user clicks the row selection checkbox.
<br />
However, you can maximize the trigger area to the entire cell holding the checkbox by setting the{' '}
<Code>selectionTrigger</Code> property value to <Code>cell</Code>:
</Txt>
<CodeBlock code={code['CellTriggerRecordsSelectionExample.tsx']} />
<Txt>In this case, clicking anywhere in the cell will result in selecting/deselecting the underlying record:</Txt>
<CellTriggerRecordsSelectionExample />
<Txt>Head over to the next example to discover more features.</Txt>
<PageNavigation of={PATH} />
</>
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mantine-datatable",
"version": "7.1.5",
"version": "7.1.6",
"description": "The lightweight, dependency-free, dark-theme aware table component for your Mantine UI data-rich applications, featuring asynchronous data loading support, pagination, intuitive Gmail-style additive batch rows selection, column sorting, custom cell data rendering, row expansion, nesting, context menus, and much more",
"keywords": [
"mantine",
Expand Down Expand Up @@ -93,15 +93,15 @@
"cssnano": "^6.0.1",
"dayjs": "^1.11.10",
"eslint": "^8.53.0",
"eslint-config-next": "^14.0.1",
"eslint-config-next": "^14.0.2",
"eslint-config-prettier": "^9.0.0",
"lodash": "^4.17.21",
"mantine-contextmenu": "^7.1.10",
"next": "14.0.1",
"mantine-contextmenu": "^7.1.12",
"next": "14.0.2",
"postcss": "^8.4.31",
"postcss-cli": "^10.1.0",
"postcss-import": "^15.1.0",
"postcss-preset-mantine": "^1.9.1",
"postcss-preset-mantine": "^1.10.0",
"postcss-simple-vars": "^7.0.1",
"prettier": "^3.0.3",
"react": "^18.2.0",
Expand Down
7 changes: 5 additions & 2 deletions package/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function DataTable<T>({
defaultColumnRender,
idAccessor = 'id',
records,
selectionTrigger = 'checkbox',
selectedRecords,
onSelectedRecordsChange,
isRecordSelectable,
Expand Down Expand Up @@ -305,6 +306,7 @@ export function DataTable<T>({
sortStatus={sortStatus}
sortIcons={sortIcons}
onSortStatusChange={onSortStatusChange}
selectionTrigger={selectionTrigger}
selectionVisible={selectionColumnVisible}
selectionChecked={allSelectableRecordsSelected}
selectionIndeterminate={someRecordsSelected && !allSelectableRecordsSelected}
Expand All @@ -319,10 +321,10 @@ export function DataTable<T>({
const recordId = getRecordId(record, idAccessor);
const isSelected = selectedRecordIds?.includes(recordId) || false;

let handleSelectionChange: React.ChangeEventHandler<HTMLInputElement> | undefined;
let handleSelectionChange: React.MouseEventHandler | undefined;
if (onSelectedRecordsChange && selectedRecords) {
handleSelectionChange = (e) => {
if ((e.nativeEvent as PointerEvent).shiftKey && lastSelectionChangeIndex !== null) {
if (e.nativeEvent.shiftKey && lastSelectionChangeIndex !== null) {
const targetRecords = records.filter(
index > lastSelectionChangeIndex
? (rec, idx) =>
Expand Down Expand Up @@ -358,6 +360,7 @@ export function DataTable<T>({
columns={effectiveColumns}
defaultColumnProps={defaultColumnProps}
defaultColumnRender={defaultColumnRender}
selectionTrigger={selectionTrigger}
selectionVisible={selectionColumnVisible}
selectionChecked={isSelected}
onSelectionChange={handleSelectionChange}
Expand Down
5 changes: 4 additions & 1 deletion package/DataTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { forwardRef } from 'react';
import { DataTableColumnGroupHeaderCell } from './DataTableColumnGroupHeaderCell';
import { DataTableHeaderCell } from './DataTableHeaderCell';
import { DataTableHeaderSelectorCell } from './DataTableHeaderSelectorCell';
import type { DataTableColumn, DataTableColumnGroup, DataTableSortProps } from './types';
import type { DataTableColumn, DataTableColumnGroup, DataTableSelectionTrigger, DataTableSortProps } from './types';

type DataTableHeaderProps<T> = {
className: string | undefined;
Expand All @@ -15,6 +15,7 @@ type DataTableHeaderProps<T> = {
columns: DataTableColumn<T>[];
defaultColumnProps: Omit<DataTableColumn<T>, 'accessor'> | undefined;
groups: readonly DataTableColumnGroup<T>[] | undefined;
selectionTrigger: DataTableSelectionTrigger;
selectionVisible: boolean;
selectionChecked: boolean;
selectionIndeterminate: boolean;
Expand All @@ -33,6 +34,7 @@ export const DataTableHeader = forwardRef(function DataTableHeader<T>(
columns,
defaultColumnProps,
groups,
selectionTrigger,
selectionVisible,
selectionChecked,
selectionIndeterminate,
Expand All @@ -44,6 +46,7 @@ export const DataTableHeader = forwardRef(function DataTableHeader<T>(
) {
const allRecordsSelectorCell = selectionVisible ? (
<DataTableHeaderSelectorCell
trigger={selectionTrigger}
shadowVisible={selectorCellShadowVisible}
checked={selectionChecked}
indeterminate={selectionIndeterminate}
Expand Down
11 changes: 9 additions & 2 deletions package/DataTableHeaderSelectorCell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Checkbox, TableTh, type CheckboxProps } from '@mantine/core';
import clsx from 'clsx';
import type { DataTableSelectionTrigger } from './types';
import { POINTER_CURSOR } from './utilityClasses';

type DataTableHeaderSelectorCellProps = {
trigger: DataTableSelectionTrigger;
shadowVisible: boolean;
checked: boolean;
indeterminate: boolean;
Expand All @@ -11,21 +14,25 @@ type DataTableHeaderSelectorCellProps = {
};

export function DataTableHeaderSelectorCell({
trigger,
shadowVisible,
checked,
indeterminate,
checkboxProps,
onChange,
rowSpan,
}: DataTableHeaderSelectorCellProps) {
const enabled = !checkboxProps.disabled;

return (
<TableTh
className="mantine-datatable-header-selector-cell"
className={clsx('mantine-datatable-header-selector-cell', { [POINTER_CURSOR]: trigger === 'cell' && enabled })}
rowSpan={rowSpan}
data-shadow-visible={shadowVisible || undefined}
onClick={trigger === 'cell' && enabled ? onChange : undefined}
>
<Checkbox
classNames={{ input: POINTER_CURSOR }}
classNames={enabled ? { input: POINTER_CURSOR } : undefined}
checked={checked}
indeterminate={indeterminate}
onChange={onChange}
Expand Down
6 changes: 5 additions & 1 deletion package/DataTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
DataTableColumn,
DataTableDefaultColumnProps,
DataTableRowClickHandler,
DataTableSelectionTrigger,
} from './types';
import { POINTER_CURSOR } from './utilityClasses';

Expand All @@ -21,9 +22,10 @@ type DataTableRowProps<T> = {
defaultColumnRender:
| ((record: T, index: number, accessor: keyof T | (string & NonNullable<unknown>)) => React.ReactNode)
| undefined;
selectionTrigger: DataTableSelectionTrigger;
selectionVisible: boolean;
selectionChecked: boolean;
onSelectionChange: React.ChangeEventHandler<HTMLInputElement> | undefined;
onSelectionChange: React.MouseEventHandler | undefined;
isRecordSelectable: ((record: T, index: number) => boolean) | undefined;
getSelectionCheckboxProps: (record: T, index: number) => CheckboxProps;
onClick: DataTableRowClickHandler<T> | undefined;
Expand All @@ -49,6 +51,7 @@ export function DataTableRow<T>({
columns,
defaultColumnProps,
defaultColumnRender,
selectionTrigger,
selectionVisible,
selectionChecked,
onSelectionChange,
Expand Down Expand Up @@ -105,6 +108,7 @@ export function DataTableRow<T>({
<DataTableRowSelectorCell<T>
record={record}
index={index}
trigger={selectionTrigger}
withRightShadow={selectorCellShadowVisible}
checked={selectionChecked}
disabled={!onSelectionChange || (isRecordSelectable ? !isRecordSelectable(record, index) : false)}
Expand Down
28 changes: 24 additions & 4 deletions package/DataTableRowSelectorCell.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,50 @@
import { Checkbox, TableTd, type CheckboxProps } from '@mantine/core';
import clsx from 'clsx';
import type { DataTableSelectionTrigger } from './types';
import { POINTER_CURSOR } from './utilityClasses';

type DataTableRowSelectorCellProps<T> = {
record: T;
index: number;
trigger: DataTableSelectionTrigger;
withRightShadow: boolean;
checked: boolean;
disabled: boolean;
onChange: React.ChangeEventHandler<HTMLInputElement> | undefined;
onChange: React.MouseEventHandler | undefined;
getCheckboxProps: (record: T, index: number) => CheckboxProps;
};

export function DataTableRowSelectorCell<T>({
record,
index,
trigger,
onChange,
withRightShadow,
getCheckboxProps,
...otherProps
}: DataTableRowSelectorCellProps<T>) {
const checkboxProps = getCheckboxProps(record, index);
const enabled = !otherProps.disabled && !checkboxProps.disabled;

const handleClick: React.MouseEventHandler = (e) => {
e.stopPropagation();
if (trigger === 'cell' && enabled) {
onChange?.(e);
}
};

return (
<TableTd
className="mantine-datatable-row-selector-cell"
className={clsx('mantine-datatable-row-selector-cell', { [POINTER_CURSOR]: trigger === 'cell' && enabled })}
data-shadow-visible={withRightShadow || undefined}
onClick={(e) => e.stopPropagation()}
onClick={handleClick}
>
<Checkbox classNames={{ input: POINTER_CURSOR }} {...otherProps} {...getCheckboxProps(record, index)} />
<Checkbox
classNames={enabled ? { input: POINTER_CURSOR } : undefined}
onChange={onChange as unknown as React.ChangeEventHandler}
{...otherProps}
{...checkboxProps}
/>
</TableTd>
);
}
8 changes: 8 additions & 0 deletions package/types/DataTableSelectionProps.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import type { CheckboxProps } from '@mantine/core';
import type { DataTableSelectionTrigger } from './DataTableSelectionTrigger';

export type DataTableSelectionProps<T = Record<string, unknown>> =
| {
selectionTrigger?: never;
selectedRecords?: never;
onSelectedRecordsChange?: never;
isRecordSelectable?: never;
getRecordSelectionCheckboxProps?: never;
allRecordsSelectionCheckboxProps?: never;
}
| {
/**
* Defines how selection is triggered.
* @default 'checkbox'
*/
selectionTrigger?: DataTableSelectionTrigger;

/**
* Currently-selected records.
*/
Expand Down
1 change: 1 addition & 0 deletions package/types/DataTableSelectionTrigger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type DataTableSelectionTrigger = 'cell' | 'checkbox';
1 change: 1 addition & 0 deletions package/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './DataTableRowClickHandler';
export * from './DataTableRowExpansionCollapseProps';
export * from './DataTableRowExpansionProps';
export * from './DataTableSelectionProps';
export * from './DataTableSelectionTrigger';
export * from './DataTableSortProps';
export * from './DataTableSortStatus';
export * from './DataTableVerticalAlign';
Expand Down
Loading

0 comments on commit 72729a8

Please sign in to comment.