@vinorcola/dynamic-table 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/lib/DefaultTheme.d.ts +11 -0
- package/lib/DefaultTheme.js +10 -0
- package/lib/index.d.ts +11 -7
- package/lib/index.js +9 -5
- package/lib/useSelection.d.ts +20 -0
- package/lib/useSelection.js +30 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
## Installation
|
|
3
|
+
|
|
4
|
+
```
|
|
5
|
+
npm install @vinorcola/dynamic-table
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
Then, if you use TailwindCSS, you must source the stylesheet file of the library within your file:
|
|
9
|
+
|
|
10
|
+
```css
|
|
11
|
+
/* Example of style.css */
|
|
12
|
+
@import "tailwindcss";
|
|
13
|
+
@source "../node_modules/@vinorcola/dynamic-table";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If you want to customize rendering, you can create a new file that re-export the dynamic table with new components:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
```
|
package/lib/DefaultTheme.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export declare function ColumnsPopup<Item extends BaseItem>(props: ColumnsPopupP
|
|
|
21
21
|
export declare function Table(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
22
22
|
export declare function HeaderContainer(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
23
23
|
export declare function HeaderLine(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export interface SelectionHeaderProps extends Props {
|
|
25
|
+
totalSelected: number;
|
|
26
|
+
unvisibleSelected: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function SelectionHeader(props: SelectionHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
24
29
|
export interface HeaderProps<Item extends BaseItem, Value extends Primitive> extends Props {
|
|
25
30
|
column: InternalColumn<Item, Value>;
|
|
26
31
|
}
|
|
@@ -51,6 +56,11 @@ export interface SelectionFilterPopupProps<Value extends Primitive> {
|
|
|
51
56
|
export declare function SelectionFilterPopup<Value extends Primitive>(props: SelectionFilterPopupProps<Value>): import("react/jsx-runtime").JSX.Element;
|
|
52
57
|
export declare function BodyContainer(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
53
58
|
export declare function Line(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
59
|
+
interface SelectionCellProps extends Props {
|
|
60
|
+
selected: boolean;
|
|
61
|
+
onToggle: () => void;
|
|
62
|
+
}
|
|
63
|
+
export declare function SelectionCell(props: SelectionCellProps): import("react/jsx-runtime").JSX.Element;
|
|
54
64
|
export declare function Cell(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
55
65
|
export interface ClickableProps extends Props {
|
|
56
66
|
target: string;
|
|
@@ -108,3 +118,4 @@ export interface PopupProps extends Props {
|
|
|
108
118
|
}
|
|
109
119
|
export declare function Popup(props: PopupProps): import("react/jsx-runtime").JSX.Element;
|
|
110
120
|
export declare function Loader(): import("react/jsx-runtime").JSX.Element;
|
|
121
|
+
export {};
|
package/lib/DefaultTheme.js
CHANGED
|
@@ -61,6 +61,9 @@ export function HeaderContainer(props) {
|
|
|
61
61
|
export function HeaderLine(props) {
|
|
62
62
|
return _jsx("tr", { className: "bg-table-header", children: props.children });
|
|
63
63
|
}
|
|
64
|
+
export function SelectionHeader(props) {
|
|
65
|
+
return (_jsxs("th", { className: "relative", children: [props.totalSelected, _jsxs("span", { className: "text-sm text-stone-500 italic", children: ["(", props.unvisibleSelected, " cach\u00E9", props.unvisibleSelected > 1 ? "s" : "", ")"] })] }));
|
|
66
|
+
}
|
|
64
67
|
export function Header(props) {
|
|
65
68
|
const filterPopup = usePopup();
|
|
66
69
|
const column = props.column;
|
|
@@ -106,6 +109,13 @@ export function BodyContainer(props) {
|
|
|
106
109
|
export function Line(props) {
|
|
107
110
|
return (_jsx("tr", { className: "even:[&>*]:bg-table-line-even odd:[&>*]:bg-table-line-odd hover:bg-table-line-hover", children: props.children }));
|
|
108
111
|
}
|
|
112
|
+
export function SelectionCell(props) {
|
|
113
|
+
return (_jsx("td", { onClick: (event) => {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
props.onToggle();
|
|
117
|
+
}, children: props.selected ? _jsx(CheckedIcon, {}) : _jsx(UncheckedIcon, {}) }));
|
|
118
|
+
}
|
|
109
119
|
export function Cell(props) {
|
|
110
120
|
return _jsx("td", { children: props.children });
|
|
111
121
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -3,19 +3,20 @@ import type { ColumnDefinition } from "./ColumnDefinition.js";
|
|
|
3
3
|
import { type FilterState } from "./useFilterState.js";
|
|
4
4
|
import type { ColumnsMaskState } from "./useMaskableColumns.js";
|
|
5
5
|
import { type PaginationState } from "./usePagination.js";
|
|
6
|
+
import { type SelectionOptions } from "./useSelection.js";
|
|
6
7
|
import { type SortState } from "./useSortState.js";
|
|
7
8
|
export type Primitive = boolean | Date | number | string;
|
|
8
9
|
export type BaseItem = Record<string, any> & {
|
|
9
10
|
readonly id: ReactKey;
|
|
10
11
|
};
|
|
11
12
|
export type ItemKey<Item extends BaseItem> = Extract<keyof Item, string>;
|
|
12
|
-
export type { ColumnDefinition, ValueResolver } from "./ColumnDefinition";
|
|
13
|
-
export { default as Dictionary } from "./Dictionary";
|
|
14
|
-
export type { FilterState } from "./useFilterState";
|
|
15
|
-
export type { SortDirection, SortState } from "./useSortState";
|
|
16
|
-
export type { ColumnsMaskState } from "./useMaskableColumns";
|
|
17
|
-
export type { PaginationState } from "./usePagination";
|
|
18
|
-
interface
|
|
13
|
+
export type { ColumnDefinition, ValueResolver } from "./ColumnDefinition.js";
|
|
14
|
+
export { default as Dictionary } from "./Dictionary.js";
|
|
15
|
+
export type { FilterState } from "./useFilterState.js";
|
|
16
|
+
export type { SortDirection, SortState } from "./useSortState.js";
|
|
17
|
+
export type { ColumnsMaskState } from "./useMaskableColumns.js";
|
|
18
|
+
export type { PaginationState } from "./usePagination.js";
|
|
19
|
+
interface BaseProps<Item extends BaseItem> {
|
|
19
20
|
items: Item[];
|
|
20
21
|
columns: ColumnDefinition<Item, Primitive>[];
|
|
21
22
|
itemTarget?: (item: Item) => string;
|
|
@@ -24,6 +25,7 @@ interface Props<Item extends BaseItem> {
|
|
|
24
25
|
initialColumnsMaskState?: ColumnsMaskState;
|
|
25
26
|
initialPaginationState?: PaginationState;
|
|
26
27
|
}
|
|
28
|
+
type Props<Item extends BaseItem> = BaseProps<Item> & SelectionOptions<Item>;
|
|
27
29
|
declare function DynamicTable<Item extends BaseItem>(props: Props<Item>): import("react/jsx-runtime").JSX.Element;
|
|
28
30
|
declare namespace DynamicTable {
|
|
29
31
|
var TableContainer: typeof import("./DefaultTheme.js").TableContainer;
|
|
@@ -32,6 +34,7 @@ declare namespace DynamicTable {
|
|
|
32
34
|
var Table: typeof import("./DefaultTheme.js").Table;
|
|
33
35
|
var HeaderContainer: typeof import("./DefaultTheme.js").HeaderContainer;
|
|
34
36
|
var HeaderLine: typeof import("./DefaultTheme.js").HeaderLine;
|
|
37
|
+
var SelectionHeader: typeof import("./DefaultTheme.js").SelectionHeader;
|
|
35
38
|
var Header: typeof import("./DefaultTheme.js").Header;
|
|
36
39
|
var HeaderActions: typeof import("./DefaultTheme.js").HeaderActions;
|
|
37
40
|
var HeaderStatus: typeof import("./DefaultTheme.js").HeaderStatus;
|
|
@@ -39,6 +42,7 @@ declare namespace DynamicTable {
|
|
|
39
42
|
var SelectionFilterPopup: typeof import("./DefaultTheme.js").SelectionFilterPopup;
|
|
40
43
|
var BodyContainer: typeof import("./DefaultTheme.js").BodyContainer;
|
|
41
44
|
var Line: typeof import("./DefaultTheme.js").Line;
|
|
45
|
+
var SelectionCell: typeof import("./DefaultTheme.js").SelectionCell;
|
|
42
46
|
var Cell: typeof import("./DefaultTheme.js").Cell;
|
|
43
47
|
var ClickableCell: typeof import("./DefaultTheme.js").ClickableCell;
|
|
44
48
|
var FooterContainer: typeof import("./DefaultTheme.js").FooterContainer;
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { BodyContainer, Button, Cell, ClickableCell, ColumnsPopup, Controller, FooterContainer, Header, HeaderActions, HeaderContainer, HeaderLine, HeaderStatus, ItemsPerPageSelector, Line, Loader, PageButton, PageSelector, Popup, SearchFilterPopup, SelectionFilterPopup, Table, TableContainer, } from "./DefaultTheme.js";
|
|
2
|
+
import { BodyContainer, Button, Cell, ClickableCell, ColumnsPopup, Controller, FooterContainer, Header, HeaderActions, HeaderContainer, HeaderLine, HeaderStatus, ItemsPerPageSelector, Line, Loader, PageButton, PageSelector, Popup, SearchFilterPopup, SelectionCell, SelectionFilterPopup, SelectionHeader, Table, TableContainer, } from "./DefaultTheme.js";
|
|
3
3
|
import UniquePopupProvider from "./UniquePopupProvider.js";
|
|
4
4
|
import useColumns from "./useColumns.js";
|
|
5
5
|
import useFilterState from "./useFilterState.js";
|
|
6
6
|
import useItems from "./useItems.js";
|
|
7
7
|
import useMaskableColumns from "./useMaskableColumns.js";
|
|
8
8
|
import usePagination from "./usePagination.js";
|
|
9
|
+
import useSelection from "./useSelection.js";
|
|
9
10
|
import useSortState from "./useSortState.js";
|
|
10
|
-
export { default as Dictionary } from "./Dictionary";
|
|
11
|
+
export { default as Dictionary } from "./Dictionary.js";
|
|
11
12
|
export default function DynamicTable(props) {
|
|
12
13
|
const columns = useColumns(props.columns);
|
|
13
14
|
const items = useItems(props.items, props.itemTarget, columns);
|
|
@@ -15,9 +16,10 @@ export default function DynamicTable(props) {
|
|
|
15
16
|
const { columns: sortedColumns, items: sortedItems, clearSortState, } = useSortState(filteredColumns, filteredItems, props.initialSortState);
|
|
16
17
|
const { allColumns, columns: displayedColumns, items: displayedItems, } = useMaskableColumns(sortedColumns, sortedItems, props.initialColumnsMaskState);
|
|
17
18
|
const { items: paginatedItems, itemsPerPage, onItemsPerPageChange, totalPages, currentPage, onCurrentPageChange, } = usePagination(displayedItems, props.initialPaginationState);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const { isInSelectionMode, totalSelectedQuantity, unvisibleSelectedQuantity, isItemSelected, onItemSelectionToggle, } = useSelection(props, paginatedItems);
|
|
20
|
+
return (_jsx(UniquePopupProvider, { children: _jsxs(DynamicTable.TableContainer, { children: [_jsx(DynamicTable.Controller, { columns: allColumns, clearFilterState: clearFilterState, clearSortState: clearSortState }), _jsxs(DynamicTable.Table, { children: [_jsx(DynamicTable.HeaderContainer, { children: _jsxs(DynamicTable.HeaderLine, { children: [isInSelectionMode && (_jsx(DynamicTable.SelectionHeader, { totalSelected: totalSelectedQuantity, unvisibleSelected: unvisibleSelectedQuantity })), displayedColumns.map((column) => (_jsx(DynamicTable.Header, { column: column, children: column.title }, column.id)))] }) }), _jsx(DynamicTable.BodyContainer, { children: paginatedItems.map((item) => (_jsxs(DynamicTable.Line, { children: [isInSelectionMode && (_jsx(DynamicTable.SelectionCell, { selected: isItemSelected(item), onToggle: onItemSelectionToggle(item) })), item.target !== null
|
|
21
|
+
? item.values.map((value) => (_jsx(DynamicTable.ClickableCell, { target: item.target, children: value.loading ? _jsx(DynamicTable.Loader, {}) : value.display }, value.column)))
|
|
22
|
+
: item.values.map((value) => (_jsx(DynamicTable.Cell, { children: value.loading ? _jsx(DynamicTable.Loader, {}) : value.display }, value.column)))] }, item.key))) }), _jsx(DynamicTable.FooterContainer, { totalColums: displayedColumns.length, pageSelector: _jsx(DynamicTable.PageSelector, { totalPages: totalPages, currentPage: currentPage, onCurrentPageChange: onCurrentPageChange }), itemsPerPageSelector: _jsx(DynamicTable.ItemsPerPageSelector, { itemsPerPage: itemsPerPage, onItemsPerPageChange: onItemsPerPageChange }) })] })] }) }));
|
|
21
23
|
}
|
|
22
24
|
DynamicTable.TableContainer = TableContainer;
|
|
23
25
|
DynamicTable.Controller = Controller;
|
|
@@ -25,6 +27,7 @@ DynamicTable.ColumnsPopup = ColumnsPopup;
|
|
|
25
27
|
DynamicTable.Table = Table;
|
|
26
28
|
DynamicTable.HeaderContainer = HeaderContainer;
|
|
27
29
|
DynamicTable.HeaderLine = HeaderLine;
|
|
30
|
+
DynamicTable.SelectionHeader = SelectionHeader;
|
|
28
31
|
DynamicTable.Header = Header;
|
|
29
32
|
DynamicTable.HeaderActions = HeaderActions;
|
|
30
33
|
DynamicTable.HeaderStatus = HeaderStatus;
|
|
@@ -32,6 +35,7 @@ DynamicTable.SearchFilterPopup = SearchFilterPopup;
|
|
|
32
35
|
DynamicTable.SelectionFilterPopup = SelectionFilterPopup;
|
|
33
36
|
DynamicTable.BodyContainer = BodyContainer;
|
|
34
37
|
DynamicTable.Line = Line;
|
|
38
|
+
DynamicTable.SelectionCell = SelectionCell;
|
|
35
39
|
DynamicTable.Cell = Cell;
|
|
36
40
|
DynamicTable.ClickableCell = ClickableCell;
|
|
37
41
|
DynamicTable.FooterContainer = FooterContainer;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BaseItem } from "./index.js";
|
|
2
|
+
import type { InternalItem, InternalItems } from "./useItems.js";
|
|
3
|
+
type ItemSelection<Item extends BaseItem> = Set<Item["id"]>;
|
|
4
|
+
interface SelectableOptions<Item extends BaseItem> {
|
|
5
|
+
selectedItems: ItemSelection<Item>;
|
|
6
|
+
onSelectionChange: (selected: ItemSelection<Item>) => void;
|
|
7
|
+
}
|
|
8
|
+
interface NonSelectableOptions {
|
|
9
|
+
selectedItems: never;
|
|
10
|
+
onSelectionChange: never;
|
|
11
|
+
}
|
|
12
|
+
export type SelectionOptions<Item extends BaseItem> = SelectableOptions<Item> | NonSelectableOptions;
|
|
13
|
+
export default function useSelection<Item extends BaseItem>(options: SelectionOptions<Item>, visibleItems: InternalItems<Item>): {
|
|
14
|
+
isInSelectionMode: boolean;
|
|
15
|
+
totalSelectedQuantity: number;
|
|
16
|
+
unvisibleSelectedQuantity: number;
|
|
17
|
+
isItemSelected: (item: InternalItem<Item>) => boolean;
|
|
18
|
+
onItemSelectionToggle: (item: InternalItem<Item>) => () => void;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
function inSelectionMode(props) {
|
|
3
|
+
return (props.selectedItems !== undefined &&
|
|
4
|
+
props.onSelectionChange !== undefined);
|
|
5
|
+
}
|
|
6
|
+
export default function useSelection(options, visibleItems) {
|
|
7
|
+
const isInSelectionMode = inSelectionMode(options);
|
|
8
|
+
const visibleSelected = useMemo(() => isInSelectionMode
|
|
9
|
+
? visibleItems.reduce((quantity, item) => quantity - (options.selectedItems.has(item.item.id) ? 1 : 0), 0)
|
|
10
|
+
: 0, [isInSelectionMode, visibleItems, options.selectedItems]);
|
|
11
|
+
return {
|
|
12
|
+
isInSelectionMode,
|
|
13
|
+
totalSelectedQuantity: options.selectedItems.size,
|
|
14
|
+
unvisibleSelectedQuantity: options.selectedItems.size - visibleSelected,
|
|
15
|
+
isItemSelected: useCallback((item) => isInSelectionMode && options.selectedItems.has(item.item.id), [isInSelectionMode, options.selectedItems]),
|
|
16
|
+
onItemSelectionToggle: useCallback((item) => () => {
|
|
17
|
+
if (!isInSelectionMode) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const selection = new Set(options.selectedItems);
|
|
21
|
+
if (selection.has(item.item.id)) {
|
|
22
|
+
selection.delete(item.item.id);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
selection.add(item.item.id);
|
|
26
|
+
}
|
|
27
|
+
options.onSelectionChange(selection);
|
|
28
|
+
}, [isInSelectionMode, options]),
|
|
29
|
+
};
|
|
30
|
+
}
|