Skip to content

Commit

Permalink
Added selectionMode parameter to support single row selection (#324)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Beckemeyer <m.beckemeyer@conterra.de>
  • Loading branch information
SvenReissig and mbeckem committed Jun 4, 2024
1 parent b5bb7a1 commit fbd12d6
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 96 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-chairs-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@open-pioneer/result-list": minor
---

Added the possibility to configure single or multi row selection mode.
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/packages/result-list/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ import React, { useEffect, useMemo, useState } from "react";
import { ColumnResizer } from "./ColumnResizer";
import { ColumnSortIndicator } from "./ColumnSortIndicator";
import { useSetupTable } from "./useSetupTable";
import { ResultListSelectionChangeEvent } from "../ResultList";
import { ResultListSelectionChangeEvent, SelectionMode } from "../ResultList";
const LOG = createLogger("result-list:DataTable");

export interface DataTableProps<Data extends BaseFeature> {
data: Data[];
columns: ColumnDef<Data>[];
selectionMode: SelectionMode;
onSelectionChange?(event: ResultListSelectionChangeEvent): void;
}

Expand Down
49 changes: 0 additions & 49 deletions src/packages/result-list/DataTable/SelectCheckbox.tsx

This file was deleted.

52 changes: 52 additions & 0 deletions src/packages/result-list/DataTable/SelectComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)
// SPDX-License-Identifier: Apache-2.0
import { Checkbox, Radio, Tooltip, chakra } from "@open-pioneer/chakra-integration";
import { ChangeEvent, useId, useMemo } from "react";

export interface SelectComponentProps {
mode?: "checkbox" | "radio";
toolTipLabel?: string;

className?: string;
"aria-label"?: string;
isIndeterminate?: boolean;
isChecked?: boolean;
isDisabled?: boolean;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
}

export function SelectComponent({
mode = "checkbox",
toolTipLabel,
...props
}: SelectComponentProps) {
const Component = mode === "checkbox" ? Checkbox : SelectRadio;
const renderedComponent = useMemo(() => {
return <Component {...props} />;
}, [Component, props]);
if (!toolTipLabel) {
return renderedComponent;
}

return (
<Tooltip label={toolTipLabel} placement="right" closeOnClick={false}>
<chakra.span
/*
wrap into span to fix tooltip around checkbox, see https://github.com/chakra-ui/chakra-ui/issues/6353
not using "shouldWrapChildren" because that also introduces a _focusable_ span (we only want the checkbox)
*/
>
{renderedComponent}
</chakra.span>
</Tooltip>
);
}

function SelectRadio(props: Omit<SelectComponentProps, "mode">) {
const id = useId();
const { isIndeterminate, ...rest } = props;
void isIndeterminate; // ignored, not supported by radio button

/** Name seems to be required for screen reader tabbing support. */
return <Radio name={id} {...rest} />;
}
17 changes: 13 additions & 4 deletions src/packages/result-list/DataTable/createColumns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ it("expect createColumn to create columns correctly", async () => {
const resultListColumns = createResultListColumns();

// Slice away the selection checkbox column
const columns = createColumns({ columns: resultListColumns, intl: intl }).slice(1);
const columns = createColumns({
columns: resultListColumns,
intl: intl,
selectionMode: "multi",
selectionStyle: "checkbox"
}).slice(1);
expect(columns.length).toEqual(resultListColumns.length);
const [simplePropColumn, colWithDisplayName, colWithWidth, colWithGetter] = columns;

Expand All @@ -39,9 +44,13 @@ it("expect createColumn to distribute remaining width on columns with undefined
const metaData = dummyMetaDataMissingWidth;
const fullWidth = 1000;
// Slice away the selection checkbox column
const columns = createColumns({ columns: metaData, intl: intl, tableWidth: fullWidth }).slice(
1
);
const columns = createColumns({
columns: metaData,
intl: intl,
tableWidth: fullWidth,
selectionMode: "multi",
selectionStyle: "checkbox"
}).slice(1);
const expectedWidth = (fullWidth - SELECT_COLUMN_SIZE - 300) / 2;
expect(columns[0]?.size).toEqual(metaData[0]!.width);
expect(columns[1]?.size).toEqual(expectedWidth);
Expand Down
34 changes: 23 additions & 11 deletions src/packages/result-list/DataTable/createColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { BaseFeature } from "@open-pioneer/map";
import { PackageIntl } from "@open-pioneer/runtime";
import { createColumnHelper } from "@tanstack/react-table";
import { Table as TanstackTable } from "@tanstack/table-core/build/lib/types";
import { FormatOptions, ResultColumn } from "../ResultList";
import { SelectCheckbox } from "./SelectCheckbox";
import { FormatOptions, ResultColumn, SelectionMode } from "../ResultList";
import { SelectComponent } from "./SelectComponent";

export const SELECT_COLUMN_SIZE = 70;

Expand All @@ -17,13 +17,15 @@ export interface CreateColumnsOptions {
intl: PackageIntl;
tableWidth?: number;
formatOptions?: FormatOptions;
selectionMode: SelectionMode;
selectionStyle: "radio" | "checkbox";
}

export function createColumns(options: CreateColumnsOptions) {
const { columns, intl, tableWidth, formatOptions } = options;
const { columns, intl, tableWidth, formatOptions, selectionMode, selectionStyle } = options;
const remainingColumnWidth: number | undefined =
tableWidth === undefined ? undefined : calcRemainingColumnWidth(columns, tableWidth);
const selectionColumn = createSelectionColumn(intl);
const selectionColumn = createSelectionColumn(intl, selectionMode, selectionStyle);
const columnDefs = columns.map((column, index) => {
const columnWidth = column.width || remainingColumnWidth;
const configuredId =
Expand Down Expand Up @@ -112,21 +114,26 @@ function renderFunc(cellValue: unknown, intl: PackageIntl, formatOptions?: Forma
}
}

function createSelectionColumn(intl: PackageIntl) {
function createSelectionColumn(
intl: PackageIntl,
selectionMode: SelectionMode,
selectionStyle: "radio" | "checkbox"
) {
return columnHelper.display({
id: "selection-buttons",
size: SELECT_COLUMN_SIZE,
enableSorting: false,
header: ({ table }) => {
if (selectionMode !== "multi") return;
return (
<chakra.div
display="inline-block"
onClick={(e) => {
e.stopPropagation();
}}
className="result-list-select-all-checkbox-container"
className="result-list-select-all-container"
>
<SelectCheckbox
<SelectComponent
className="result-list-select-all-checkbox"
isChecked={table.getIsAllRowsSelected()}
isIndeterminate={table.getIsSomeRowsSelected()}
Expand All @@ -137,21 +144,26 @@ function createSelectionColumn(intl: PackageIntl) {
);
},
cell: ({ row }) => {
const className =
selectionStyle === "radio"
? "result-list-select-row-radio"
: "result-list-select-row-checkbox";
return (
<chakra.div
display="inline-block"
onClick={(e) => {
e.stopPropagation();
}}
className="result-list-select-row-checkbox-container"
className="result-list-select-row-container"
>
<SelectCheckbox
className="result-list-select-row-checkbox"
<SelectComponent
mode={selectionStyle}
className={className}
isChecked={row.getIsSelected()}
isDisabled={!row.getCanSelect()}
isIndeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
ariaLabel={intl.formatMessage({
aria-label={intl.formatMessage({
id: "ariaLabel.selectSingle"
})}
/>
Expand Down
13 changes: 11 additions & 2 deletions src/packages/result-list/DataTable/useSetupTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
getSortedRowModel,
useReactTable
} from "@tanstack/react-table";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { usePrevious } from "react-use";
import { DataTableProps } from "./DataTable";

export function useSetupTable<Data extends BaseFeature>(props: DataTableProps<Data>) {
const { data, columns, onSelectionChange: onSelectionChange } = props;
const { data, columns, onSelectionChange: onSelectionChange, selectionMode } = props;
const [sorting, setSorting] = useState<SortingState>([]);
const previousSelectionMode = usePrevious(props.selectionMode);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});

// Only sort by columns which actually exist
Expand Down Expand Up @@ -65,6 +67,7 @@ export function useSetupTable<Data extends BaseFeature>(props: DataTableProps<Da
columnResizeMode: "onChange",
getCoreRowModel: getCoreRowModel(),
enableRowSelection: true,
enableMultiRowSelection: selectionMode === "multi",
onRowSelectionChange: updateSelection,
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
Expand All @@ -74,6 +77,12 @@ export function useSetupTable<Data extends BaseFeature>(props: DataTableProps<Da
}
});

useEffect(() => {
if (previousSelectionMode && selectionMode !== previousSelectionMode) {
table.resetRowSelection();
}
}, [table, previousSelectionMode, selectionMode]);

return { table, sorting, rowSelection };
}

Expand Down
24 changes: 22 additions & 2 deletions src/packages/result-list/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,28 @@ const ownHighlightStyle = {

### Selection

Users can select (and deselect) individual features by clicking on the checkbox at the beginning of a row.
A checkbox in the header of the table allows to select (or deselect) _all_ features in the table.
Users can select (and deselect) individual features by clicking on the selection control
(checkbox or radio button) at the beginning of a row.

The optional property `selectionMode` can be used to change the default selection mode "multi" to "single".
Single selection means that only one row can be selected at a time.
With the `selectionMode` `"single"`, the selection control is a radio button by default.

A checkbox in the header of the table allows to select (or deselect) _all_ features in the table if
the `selectionMode` is "multi" (default).

```tsx
import { ResultList } from "@open-pioneer/result-list";
<ResultList mapId={mapId} input={input} selectionMode="single" />;
```

The style of the selection control can be configured by using the `"selectionStyle"` property (`"checkbox"` or `"radio`).
Note that the combination of `selectionMode` `"single"` with `selectionStyle` `"radio"` is not allowed:

```tsx
import { ResultList } from "@open-pioneer/result-list";
<ResultList mapId={mapId} input={input} selectionMode="single" selectionStyle="checkbox" />;
```

### Listening for selection changes

Expand Down
Loading

0 comments on commit fbd12d6

Please sign in to comment.