@douglasneuroinformatics/libui 2.2.4 → 2.3.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/dist/components/ActionDropdown/ActionDropdown.d.ts +20 -0
- package/dist/components/ActionDropdown/ActionDropdown.d.ts.map +1 -0
- package/dist/components/ActionDropdown/ActionDropdown.js +15 -0
- package/dist/components/ClientTable/ClientTable.d.ts +33 -0
- package/dist/components/ClientTable/ClientTable.d.ts.map +1 -0
- package/dist/components/ClientTable/ClientTable.js +69 -0
- package/dist/components/ClientTable/ClientTablePagination.d.ts +11 -0
- package/dist/components/ClientTable/ClientTablePagination.d.ts.map +1 -0
- package/dist/components/ClientTable/ClientTablePagination.js +20 -0
- package/dist/components/ListboxDropdown/ListboxDropdown.d.ts +18 -0
- package/dist/components/ListboxDropdown/ListboxDropdown.d.ts.map +1 -0
- package/dist/components/ListboxDropdown/ListboxDropdown.js +23 -0
- package/dist/components/Sheet/Sheet.d.ts +1 -1
- package/dist/components/Sheet/SheetContent.d.ts +2 -2
- package/dist/components.d.ts +3 -4
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +3 -4
- package/package.json +1 -1
- package/src/components/ActionDropdown/ActionDropdown.tsx +60 -0
- package/src/components/ClientTable/ClientTable.tsx +165 -0
- package/src/components/ClientTable/ClientTablePagination.tsx +60 -0
- package/src/components/ListboxDropdown/ListboxDropdown.tsx +61 -0
- package/src/components.ts +3 -4
- package/dist/components/LegacyDropdown/LegacyDropdown.d.ts +0 -21
- package/dist/components/LegacyDropdown/LegacyDropdown.d.ts.map +0 -1
- package/dist/components/LegacyDropdown/LegacyDropdown.js +0 -17
- package/dist/components/LegacySelectDropdown/LegacySelectDropdown.d.ts +0 -21
- package/dist/components/LegacySelectDropdown/LegacySelectDropdown.d.ts.map +0 -1
- package/dist/components/LegacySelectDropdown/LegacySelectDropdown.js +0 -18
- package/dist/components/LegacyTable/LegacyClientTable.d.ts +0 -5
- package/dist/components/LegacyTable/LegacyClientTable.d.ts.map +0 -1
- package/dist/components/LegacyTable/LegacyClientTable.js +0 -29
- package/dist/components/LegacyTable/LegacyTable.d.ts +0 -29
- package/dist/components/LegacyTable/LegacyTable.d.ts.map +0 -1
- package/dist/components/LegacyTable/LegacyTable.js +0 -54
- package/dist/components/LegacyTable/LegacyTableColumnHeader.d.ts +0 -16
- package/dist/components/LegacyTable/LegacyTableColumnHeader.d.ts.map +0 -1
- package/dist/components/LegacyTable/LegacyTableColumnHeader.js +0 -20
- package/src/components/LegacyDropdown/LegacyDropdown.tsx +0 -79
- package/src/components/LegacySelectDropdown/LegacySelectDropdown.tsx +0 -87
- package/src/components/LegacyTable/LegacyClientTable.tsx +0 -55
- package/src/components/LegacyTable/LegacyTable.tsx +0 -118
- package/src/components/LegacyTable/LegacyTableColumnHeader.tsx +0 -71
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { DropdownMenuContentProps } from '../DropdownMenu/DropdownMenuContent.js';
|
|
3
|
+
type ActionDropdownOptions = {
|
|
4
|
+
[key: string]: string;
|
|
5
|
+
} | readonly string[];
|
|
6
|
+
type ActionDropdownOptionKey<T> = T extends readonly string[] ? T[number] : T extends {
|
|
7
|
+
[key: string]: string;
|
|
8
|
+
} ? Extract<keyof T, string> : never;
|
|
9
|
+
export type ActionDropdownProps<T extends ActionDropdownOptions> = {
|
|
10
|
+
align?: DropdownMenuContentProps['align'];
|
|
11
|
+
/** Callback function invoked when user clicks an option */
|
|
12
|
+
onSelection: (option: ActionDropdownOptionKey<T>) => void;
|
|
13
|
+
/** Either a list of options for the dropdown, or an object with options mapped to custom labels */
|
|
14
|
+
options: T;
|
|
15
|
+
/** The text content for the dropdown toggle */
|
|
16
|
+
title: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function ActionDropdown<const T extends ActionDropdownOptions>({ align, onSelection, options, title }: ActionDropdownProps<T>): React.JSX.Element;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=ActionDropdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActionDropdown.d.ts","sourceRoot":"","sources":["../../../src/components/ActionDropdown/ActionDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AAEvF,KAAK,qBAAqB,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,SAAS,MAAM,EAAE,CAAC;AAE3E,KAAK,uBAAuB,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,MAAM,EAAE,GACzD,CAAC,CAAC,MAAM,CAAC,GACT,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GACjC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GACxB,KAAK,CAAC;AAEZ,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,qBAAqB,IAAI;IACjE,KAAK,CAAC,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAE1C,2DAA2D;IAC3D,WAAW,EAAE,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAE1D,oGAAoG;IACpG,OAAO,EAAE,CAAC,CAAC;IAEX,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAGF,wBAAgB,cAAc,CAAC,KAAK,CAAC,CAAC,SAAS,qBAAqB,EAAE,EACpE,KAAe,EACf,WAAW,EACX,OAAO,EACP,KAAK,EACN,EAAE,mBAAmB,CAAC,CAAC,CAAC,qBAyBxB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DropdownButton } from '../DropdownButton/DropdownButton.js';
|
|
3
|
+
import { DropdownMenu } from '../DropdownMenu/DropdownMenu.js';
|
|
4
|
+
// eslint-disable-next-line react/function-component-definition
|
|
5
|
+
export function ActionDropdown({ align = 'start', onSelection, options, title }) {
|
|
6
|
+
const optionKeys = options instanceof Array ? options : Object.keys(options);
|
|
7
|
+
return (React.createElement(DropdownMenu, null,
|
|
8
|
+
React.createElement("div", { className: "w-full" },
|
|
9
|
+
React.createElement(DropdownMenu.Trigger, { asChild: true },
|
|
10
|
+
React.createElement(DropdownButton, null, title)),
|
|
11
|
+
React.createElement(DropdownMenu.Content, { align: align, style: { width: 'var(--radix-popper-anchor-width)' } },
|
|
12
|
+
React.createElement(DropdownMenu.Group, null, optionKeys.map((option) => (React.createElement(DropdownMenu.Item, { key: option, onClick: () => {
|
|
13
|
+
onSelection(option);
|
|
14
|
+
} }, Array.isArray(options) ? option : options[option]))))))));
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ClientTableEntry = {
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
};
|
|
5
|
+
export type ClientFieldFactory<T extends ClientTableEntry = ClientTableEntry> = (entry: T) => string;
|
|
6
|
+
export type ClientTableColumn<T extends ClientTableEntry> = {
|
|
7
|
+
/** How to determine the values for column */
|
|
8
|
+
field: ClientFieldFactory<T> | keyof T;
|
|
9
|
+
/** Override the default formatter for this field */
|
|
10
|
+
formatter?: (value: any) => string;
|
|
11
|
+
/** The label to be displayed on the header */
|
|
12
|
+
label: string;
|
|
13
|
+
};
|
|
14
|
+
export type ClientTableDropdownOptions<T extends ClientTableEntry> = {
|
|
15
|
+
icon?: React.ComponentType<Omit<React.SVGProps<SVGSVGElement>, 'ref'>>;
|
|
16
|
+
label: string;
|
|
17
|
+
onSelection: (column: ClientTableColumn<T>) => void;
|
|
18
|
+
}[];
|
|
19
|
+
export type ClientTableColumnProps<T extends ClientTableEntry> = {
|
|
20
|
+
column: ClientTableColumn<T>;
|
|
21
|
+
dropdownOptions?: ClientTableDropdownOptions<T>;
|
|
22
|
+
};
|
|
23
|
+
export type ClientTableProps<T extends ClientTableEntry> = {
|
|
24
|
+
className?: string;
|
|
25
|
+
columnDropdownOptions?: ClientTableDropdownOptions<T>;
|
|
26
|
+
columns: ClientTableColumn<T>[];
|
|
27
|
+
data: T[];
|
|
28
|
+
entriesPerPage?: number;
|
|
29
|
+
minRows?: number;
|
|
30
|
+
onEntryClick?: (entry: T) => void;
|
|
31
|
+
};
|
|
32
|
+
export declare const ClientTable: <T extends ClientTableEntry>({ className, columnDropdownOptions, columns, data, entriesPerPage, minRows, onEntryClick }: ClientTableProps<T>) => React.JSX.Element;
|
|
33
|
+
//# sourceMappingURL=ClientTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientTable.d.ts","sourceRoot":"","sources":["../../../src/components/ClientTable/ClientTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AA0BxC,MAAM,MAAM,gBAAgB,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;AAErG,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC1D,6CAA6C;IAC7C,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IAEvC,oDAAoD;IAEpD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC;IAEnC,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,SAAS,gBAAgB,IAAI;IACnE,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACrD,EAAE,CAAC;AAEJ,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC/D,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7B,eAAe,CAAC,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB,CAAC,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;IACtD,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;IAChC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,WAAW,2HAQrB,iBAAiB,CAAC,CAAC,sBA6FrB,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { toBasicISOString } from '@douglasneuroinformatics/libjs';
|
|
3
|
+
import { range } from 'lodash-es';
|
|
4
|
+
import { ChevronDownIcon } from 'lucide-react';
|
|
5
|
+
import { cn } from '../../utils.js';
|
|
6
|
+
import { DropdownMenu } from '../DropdownMenu/DropdownMenu.js';
|
|
7
|
+
import { Table } from '../Table/Table.js';
|
|
8
|
+
import { ClientTablePagination } from './ClientTablePagination.js';
|
|
9
|
+
/** Coerces the value in a cell to a string for consistant display purposes */
|
|
10
|
+
function defaultFormatter(value) {
|
|
11
|
+
if (typeof value === 'string') {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
else if (typeof value === 'number') {
|
|
15
|
+
return value.toFixed(2).toString();
|
|
16
|
+
}
|
|
17
|
+
else if (typeof value === 'undefined') {
|
|
18
|
+
return 'NA';
|
|
19
|
+
}
|
|
20
|
+
if (value instanceof Date) {
|
|
21
|
+
return toBasicISOString(value);
|
|
22
|
+
}
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
}
|
|
25
|
+
export const ClientTable = ({ className, columnDropdownOptions, columns, data, entriesPerPage = 10, minRows, onEntryClick }) => {
|
|
26
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
27
|
+
const pageCount = Math.ceil(data.length / entriesPerPage);
|
|
28
|
+
const firstEntry = data.length === 0 ? 0 : (currentPage - 1) * entriesPerPage + 1;
|
|
29
|
+
const lastEntry = Math.min(firstEntry + entriesPerPage - 1, data.length);
|
|
30
|
+
const currentEntries = data.slice(firstEntry - 1, lastEntry);
|
|
31
|
+
const nRows = Math.max(currentEntries.length, minRows ?? -1);
|
|
32
|
+
return (React.createElement("div", { className: className },
|
|
33
|
+
React.createElement("div", { className: "rounded-md border bg-card tracking-tight text-muted-foreground shadow" },
|
|
34
|
+
React.createElement(Table, null,
|
|
35
|
+
React.createElement(Table.Header, null,
|
|
36
|
+
React.createElement(Table.Row, null, columns.map((column, i) => (React.createElement(Table.Head, { className: "whitespace-nowrap text-foreground md:px-6", key: i }, columnDropdownOptions ? (React.createElement(DropdownMenu, null,
|
|
37
|
+
React.createElement(DropdownMenu.Trigger, { className: "flex items-center justify-between gap-3" },
|
|
38
|
+
React.createElement("span", null, column.label),
|
|
39
|
+
React.createElement(ChevronDownIcon, null)),
|
|
40
|
+
React.createElement(DropdownMenu.Content, { align: "start" },
|
|
41
|
+
React.createElement(DropdownMenu.Group, null, columnDropdownOptions.map((option) => {
|
|
42
|
+
const Icon = option.icon;
|
|
43
|
+
return (React.createElement(DropdownMenu.Item, { key: option.label, onClick: () => {
|
|
44
|
+
option.onSelection(column);
|
|
45
|
+
} },
|
|
46
|
+
Icon && React.createElement(Icon, { className: "mr-2", height: 16, width: 16 }),
|
|
47
|
+
option.label));
|
|
48
|
+
}))))) : (column.label)))))),
|
|
49
|
+
React.createElement(Table.Body, null, range(nRows).map((i) => {
|
|
50
|
+
const entry = currentEntries[i];
|
|
51
|
+
return (React.createElement(Table.Row, { className: cn(typeof onEntryClick === 'function' && 'cursor-pointer hover:backdrop-brightness-95'), key: i, onClick: () => {
|
|
52
|
+
onEntryClick?.(entry);
|
|
53
|
+
} }, columns.map(({ field, formatter }, j) => {
|
|
54
|
+
let value;
|
|
55
|
+
if (!entry) {
|
|
56
|
+
value = '';
|
|
57
|
+
}
|
|
58
|
+
else if (typeof field === 'function') {
|
|
59
|
+
value = field(entry);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
value = entry[field];
|
|
63
|
+
}
|
|
64
|
+
const formattedValue = entry && formatter ? formatter(value) : defaultFormatter(value);
|
|
65
|
+
return (React.createElement(Table.Cell, { className: "text-ellipsis leading-none md:px-6", key: j }, formattedValue));
|
|
66
|
+
})));
|
|
67
|
+
})))),
|
|
68
|
+
React.createElement(ClientTablePagination, { currentPage: currentPage, firstEntry: firstEntry, lastEntry: lastEntry, pageCount: pageCount, setCurrentPage: setCurrentPage, totalEntries: data.length })));
|
|
69
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ClientPagePaginationProps = {
|
|
3
|
+
currentPage: number;
|
|
4
|
+
firstEntry: number;
|
|
5
|
+
lastEntry: number;
|
|
6
|
+
pageCount: number;
|
|
7
|
+
setCurrentPage: (value: number) => void;
|
|
8
|
+
totalEntries: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const ClientTablePagination: ({ currentPage, firstEntry, lastEntry, pageCount, setCurrentPage, totalEntries }: ClientPagePaginationProps) => React.JSX.Element;
|
|
11
|
+
//# sourceMappingURL=ClientTablePagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientTablePagination.d.ts","sourceRoot":"","sources":["../../../src/components/ClientTable/ClientTablePagination.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,MAAM,MAAM,yBAAyB,GAAG;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,qBAAqB,oFAO/B,yBAAyB,sBAqC3B,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button } from '../Button/Button.js';
|
|
4
|
+
export const ClientTablePagination = ({ currentPage, firstEntry, lastEntry, pageCount, setCurrentPage, totalEntries }) => {
|
|
5
|
+
const { t } = useTranslation('libui');
|
|
6
|
+
return (React.createElement("div", { className: "flex items-center justify-between py-3" },
|
|
7
|
+
React.createElement("div", { className: "hidden sm:block" },
|
|
8
|
+
React.createElement("p", { className: "text-sm font-medium text-muted-foreground" }, t('pagination.info', {
|
|
9
|
+
first: firstEntry,
|
|
10
|
+
last: lastEntry,
|
|
11
|
+
total: totalEntries
|
|
12
|
+
}))),
|
|
13
|
+
React.createElement("div", { className: "flex flex-1 justify-between gap-3 sm:justify-end" },
|
|
14
|
+
React.createElement(Button, { disabled: currentPage === 1, type: "button", variant: "outline", onClick: () => {
|
|
15
|
+
setCurrentPage(currentPage - 1);
|
|
16
|
+
} }, t('pagination.previous')),
|
|
17
|
+
React.createElement(Button, { disabled: currentPage === pageCount, type: "button", variant: "outline", onClick: () => {
|
|
18
|
+
setCurrentPage(currentPage + 1);
|
|
19
|
+
} }, t('pagination.next')))));
|
|
20
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ButtonProps } from '../Button/Button.js';
|
|
3
|
+
export type ListboxDropdownOption = {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
};
|
|
7
|
+
export type ListboxDropdownProps<T extends ListboxDropdownOption> = {
|
|
8
|
+
checkPosition?: 'left' | 'right';
|
|
9
|
+
className?: string;
|
|
10
|
+
options: T[];
|
|
11
|
+
selected: T[];
|
|
12
|
+
setSelected: React.Dispatch<React.SetStateAction<T[]>>;
|
|
13
|
+
title: string;
|
|
14
|
+
/** The button variant to use for the dropdown toggle */
|
|
15
|
+
variant?: ButtonProps['variant'];
|
|
16
|
+
};
|
|
17
|
+
export declare const ListboxDropdown: <T extends ListboxDropdownOption>({ options, selected, setSelected, title }: ListboxDropdownProps<T>) => React.JSX.Element;
|
|
18
|
+
//# sourceMappingURL=ListboxDropdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListboxDropdown.d.ts","sourceRoot":"","sources":["../../../src/components/ListboxDropdown/ListboxDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,qBAAqB,IAAI;IAClE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,QAAQ,EAAE,CAAC,EAAE,CAAC;IACd,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,eAAe,+EAKzB,qBAAqB,CAAC,CAAC,sBAiCzB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {} from '../Button/Button.js';
|
|
3
|
+
import { DropdownButton } from '../DropdownButton/DropdownButton.js';
|
|
4
|
+
import { DropdownMenu } from '../DropdownMenu/DropdownMenu.js';
|
|
5
|
+
export const ListboxDropdown = ({ options, selected, setSelected, title }) => {
|
|
6
|
+
return (React.createElement(DropdownMenu, null,
|
|
7
|
+
React.createElement(DropdownMenu.Trigger, { asChild: true, className: "w-full" },
|
|
8
|
+
React.createElement(DropdownButton, null, title)),
|
|
9
|
+
React.createElement(DropdownMenu.Content, { widthFull: true, align: "start" }, options.map((option) => {
|
|
10
|
+
const checked = Boolean(selected.find((selectedOption) => selectedOption.key === option.key));
|
|
11
|
+
return (React.createElement(DropdownMenu.CheckboxItem, { checked: checked, className: "flex w-full items-center whitespace-nowrap bg-slate-50 p-2 text-sm hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 ", "data-cy": "select-dropdown-option", key: option.key, onSelect: (event) => {
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
if (checked) {
|
|
14
|
+
setSelected((prevSelected) => {
|
|
15
|
+
return prevSelected.filter((selectedOption) => selectedOption.key !== option.key);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
setSelected((prevSelected) => [...prevSelected, option]);
|
|
20
|
+
}
|
|
21
|
+
} }, option.label));
|
|
22
|
+
}))));
|
|
23
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export declare const Sheet: import("react").FC<import("@radix-ui/react-dialog").DialogProps> & {
|
|
3
3
|
Close: import("react").ForwardRefExoticComponent<import("@radix-ui/react-dialog").DialogCloseProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
4
4
|
Content: import("react").ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("class-variance-authority").VariantProps<(props?: ({
|
|
5
|
-
side?: "
|
|
5
|
+
side?: "top" | "right" | "bottom" | "left" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string> & import("react").RefAttributes<HTMLDivElement>>;
|
|
7
7
|
Description: import("react").ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogDescriptionProps & import("react").RefAttributes<HTMLParagraphElement>, "ref"> & import("react").RefAttributes<HTMLParagraphElement>>;
|
|
8
8
|
Footer: ({ className, ...props }: import("react").HTMLAttributes<HTMLDivElement>) => import("react").JSX.Element;
|
|
@@ -2,10 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import { Content } from '@radix-ui/react-dialog';
|
|
3
3
|
import { type VariantProps } from 'class-variance-authority';
|
|
4
4
|
export declare const sheetVariants: (props?: ({
|
|
5
|
-
side?: "
|
|
5
|
+
side?: "top" | "right" | "bottom" | "left" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export type SheetContentProps = React.ComponentPropsWithoutRef<typeof Content> & VariantProps<typeof sheetVariants>;
|
|
8
8
|
export declare const SheetContent: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-dialog").DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & VariantProps<(props?: ({
|
|
9
|
-
side?: "
|
|
9
|
+
side?: "top" | "right" | "bottom" | "left" | null | undefined;
|
|
10
10
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string> & React.RefAttributes<HTMLDivElement>>;
|
|
11
11
|
//# sourceMappingURL=SheetContent.d.ts.map
|
package/dist/components.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './components/Accordion/Accordion.js';
|
|
2
|
+
export * from './components/ActionDropdown/ActionDropdown.js';
|
|
2
3
|
export * from './components/AlertDialog/AlertDialog.js';
|
|
3
4
|
export * from './components/ArrowToggle/ArrowToggle.js';
|
|
4
5
|
export * from './components/Avatar/Avatar.js';
|
|
@@ -7,6 +8,7 @@ export * from './components/Breadcrumb/Breadcrumb.js';
|
|
|
7
8
|
export * from './components/Button/Button.js';
|
|
8
9
|
export * from './components/Card/Card.js';
|
|
9
10
|
export * from './components/Checkbox/Checkbox.js';
|
|
11
|
+
export * from './components/ClientTable/ClientTable.js';
|
|
10
12
|
export * from './components/Collapsible/Collapsible.js';
|
|
11
13
|
export * from './components/Command/Command.js';
|
|
12
14
|
export * from './components/ContextMenu/ContextMenu.js';
|
|
@@ -23,14 +25,11 @@ export * from './components/HoverCard/HoverCard.js';
|
|
|
23
25
|
export * from './components/Input/Input.js';
|
|
24
26
|
export * from './components/Label/Label.js';
|
|
25
27
|
export * from './components/LanguageToggle/LanguageToggle.js';
|
|
26
|
-
export * from './components/LegacyDropdown/LegacyDropdown.js';
|
|
27
28
|
export * from './components/LegacyModal/LegacyModal.js';
|
|
28
|
-
export * from './components/LegacySelectDropdown/LegacySelectDropdown.js';
|
|
29
29
|
export * from './components/LegacySlider/LegacySlider.js';
|
|
30
30
|
export * from './components/LegacyStepper/LegacyStepper.js';
|
|
31
|
-
export * from './components/LegacyTable/LegacyClientTable.js';
|
|
32
|
-
export * from './components/LegacyTable/LegacyTable.js';
|
|
33
31
|
export * from './components/LineGraph/LineGraph.js';
|
|
32
|
+
export * from './components/ListboxDropdown/ListboxDropdown.js';
|
|
34
33
|
export * from './components/MenuBar/MenuBar.js';
|
|
35
34
|
export * from './components/NotificationHub/NotificationHub.js';
|
|
36
35
|
export * from './components/Pagination/Pagination.js';
|
package/dist/components.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,yCAAyC,CAAC;AACxD,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+CAA+C,CAAC;AAC9D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+CAA+C,CAAC;AAC9D,cAAc
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+CAA+C,CAAC;AAC9D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+CAA+C,CAAC;AAC9D,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,qCAAqC,CAAC;AACpD,cAAc,iDAAiD,CAAC;AAChE,cAAc,iCAAiC,CAAC;AAChD,cAAc,iDAAiD,CAAC;AAChE,cAAc,uCAAuC,CAAC;AACtD,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,yCAAyC,CAAC;AACxD,cAAc,iCAAiC,CAAC"}
|
package/dist/components.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './components/Accordion/Accordion.js';
|
|
2
|
+
export * from './components/ActionDropdown/ActionDropdown.js';
|
|
2
3
|
export * from './components/AlertDialog/AlertDialog.js';
|
|
3
4
|
export * from './components/ArrowToggle/ArrowToggle.js';
|
|
4
5
|
export * from './components/Avatar/Avatar.js';
|
|
@@ -7,6 +8,7 @@ export * from './components/Breadcrumb/Breadcrumb.js';
|
|
|
7
8
|
export * from './components/Button/Button.js';
|
|
8
9
|
export * from './components/Card/Card.js';
|
|
9
10
|
export * from './components/Checkbox/Checkbox.js';
|
|
11
|
+
export * from './components/ClientTable/ClientTable.js';
|
|
10
12
|
export * from './components/Collapsible/Collapsible.js';
|
|
11
13
|
export * from './components/Command/Command.js';
|
|
12
14
|
export * from './components/ContextMenu/ContextMenu.js';
|
|
@@ -23,14 +25,11 @@ export * from './components/HoverCard/HoverCard.js';
|
|
|
23
25
|
export * from './components/Input/Input.js';
|
|
24
26
|
export * from './components/Label/Label.js';
|
|
25
27
|
export * from './components/LanguageToggle/LanguageToggle.js';
|
|
26
|
-
export * from './components/LegacyDropdown/LegacyDropdown.js';
|
|
27
28
|
export * from './components/LegacyModal/LegacyModal.js';
|
|
28
|
-
export * from './components/LegacySelectDropdown/LegacySelectDropdown.js';
|
|
29
29
|
export * from './components/LegacySlider/LegacySlider.js';
|
|
30
30
|
export * from './components/LegacyStepper/LegacyStepper.js';
|
|
31
|
-
export * from './components/LegacyTable/LegacyClientTable.js';
|
|
32
|
-
export * from './components/LegacyTable/LegacyTable.js';
|
|
33
31
|
export * from './components/LineGraph/LineGraph.js';
|
|
32
|
+
export * from './components/ListboxDropdown/ListboxDropdown.js';
|
|
34
33
|
export * from './components/MenuBar/MenuBar.js';
|
|
35
34
|
export * from './components/NotificationHub/NotificationHub.js';
|
|
36
35
|
export * from './components/Pagination/Pagination.js';
|
package/package.json
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { DropdownButton } from '../DropdownButton/DropdownButton.js';
|
|
4
|
+
import { DropdownMenu } from '../DropdownMenu/DropdownMenu.js';
|
|
5
|
+
|
|
6
|
+
import type { DropdownMenuContentProps } from '../DropdownMenu/DropdownMenuContent.js';
|
|
7
|
+
|
|
8
|
+
type ActionDropdownOptions = { [key: string]: string } | readonly string[];
|
|
9
|
+
|
|
10
|
+
type ActionDropdownOptionKey<T> = T extends readonly string[]
|
|
11
|
+
? T[number]
|
|
12
|
+
: T extends { [key: string]: string }
|
|
13
|
+
? Extract<keyof T, string>
|
|
14
|
+
: never;
|
|
15
|
+
|
|
16
|
+
export type ActionDropdownProps<T extends ActionDropdownOptions> = {
|
|
17
|
+
align?: DropdownMenuContentProps['align'];
|
|
18
|
+
|
|
19
|
+
/** Callback function invoked when user clicks an option */
|
|
20
|
+
onSelection: (option: ActionDropdownOptionKey<T>) => void;
|
|
21
|
+
|
|
22
|
+
/** Either a list of options for the dropdown, or an object with options mapped to custom labels */
|
|
23
|
+
options: T;
|
|
24
|
+
|
|
25
|
+
/** The text content for the dropdown toggle */
|
|
26
|
+
title: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line react/function-component-definition
|
|
30
|
+
export function ActionDropdown<const T extends ActionDropdownOptions>({
|
|
31
|
+
align = 'start',
|
|
32
|
+
onSelection,
|
|
33
|
+
options,
|
|
34
|
+
title
|
|
35
|
+
}: ActionDropdownProps<T>) {
|
|
36
|
+
const optionKeys: readonly string[] = options instanceof Array ? options : Object.keys(options);
|
|
37
|
+
return (
|
|
38
|
+
<DropdownMenu>
|
|
39
|
+
<div className="w-full">
|
|
40
|
+
<DropdownMenu.Trigger asChild>
|
|
41
|
+
<DropdownButton>{title}</DropdownButton>
|
|
42
|
+
</DropdownMenu.Trigger>
|
|
43
|
+
<DropdownMenu.Content align={align} style={{ width: 'var(--radix-popper-anchor-width)' }}>
|
|
44
|
+
<DropdownMenu.Group>
|
|
45
|
+
{optionKeys.map((option) => (
|
|
46
|
+
<DropdownMenu.Item
|
|
47
|
+
key={option}
|
|
48
|
+
onClick={() => {
|
|
49
|
+
onSelection(option as ActionDropdownOptionKey<T>);
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{Array.isArray(options) ? option : (options[option as keyof T] as string)}
|
|
53
|
+
</DropdownMenu.Item>
|
|
54
|
+
))}
|
|
55
|
+
</DropdownMenu.Group>
|
|
56
|
+
</DropdownMenu.Content>
|
|
57
|
+
</div>
|
|
58
|
+
</DropdownMenu>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { toBasicISOString } from '@douglasneuroinformatics/libjs';
|
|
4
|
+
import { range } from 'lodash-es';
|
|
5
|
+
import { ChevronDownIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../../utils.js';
|
|
8
|
+
import { DropdownMenu } from '../DropdownMenu/DropdownMenu.js';
|
|
9
|
+
import { Table } from '../Table/Table.js';
|
|
10
|
+
import { ClientTablePagination } from './ClientTablePagination.js';
|
|
11
|
+
|
|
12
|
+
/** Coerces the value in a cell to a string for consistant display purposes */
|
|
13
|
+
function defaultFormatter(value: unknown): string {
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
return value;
|
|
16
|
+
} else if (typeof value === 'number') {
|
|
17
|
+
return value.toFixed(2).toString();
|
|
18
|
+
} else if (typeof value === 'undefined') {
|
|
19
|
+
return 'NA';
|
|
20
|
+
}
|
|
21
|
+
if (value instanceof Date) {
|
|
22
|
+
return toBasicISOString(value);
|
|
23
|
+
}
|
|
24
|
+
return JSON.stringify(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ClientTableEntry = { [key: string]: unknown };
|
|
28
|
+
|
|
29
|
+
export type ClientFieldFactory<T extends ClientTableEntry = ClientTableEntry> = (entry: T) => string;
|
|
30
|
+
|
|
31
|
+
export type ClientTableColumn<T extends ClientTableEntry> = {
|
|
32
|
+
/** How to determine the values for column */
|
|
33
|
+
field: ClientFieldFactory<T> | keyof T;
|
|
34
|
+
|
|
35
|
+
/** Override the default formatter for this field */
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
formatter?: (value: any) => string;
|
|
38
|
+
|
|
39
|
+
/** The label to be displayed on the header */
|
|
40
|
+
label: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ClientTableDropdownOptions<T extends ClientTableEntry> = {
|
|
44
|
+
icon?: React.ComponentType<Omit<React.SVGProps<SVGSVGElement>, 'ref'>>;
|
|
45
|
+
label: string;
|
|
46
|
+
onSelection: (column: ClientTableColumn<T>) => void;
|
|
47
|
+
}[];
|
|
48
|
+
|
|
49
|
+
export type ClientTableColumnProps<T extends ClientTableEntry> = {
|
|
50
|
+
column: ClientTableColumn<T>;
|
|
51
|
+
dropdownOptions?: ClientTableDropdownOptions<T>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ClientTableProps<T extends ClientTableEntry> = {
|
|
55
|
+
className?: string;
|
|
56
|
+
columnDropdownOptions?: ClientTableDropdownOptions<T>;
|
|
57
|
+
columns: ClientTableColumn<T>[];
|
|
58
|
+
data: T[];
|
|
59
|
+
entriesPerPage?: number;
|
|
60
|
+
minRows?: number;
|
|
61
|
+
onEntryClick?: (entry: T) => void;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const ClientTable = <T extends ClientTableEntry>({
|
|
65
|
+
className,
|
|
66
|
+
columnDropdownOptions,
|
|
67
|
+
columns,
|
|
68
|
+
data,
|
|
69
|
+
entriesPerPage = 10,
|
|
70
|
+
minRows,
|
|
71
|
+
onEntryClick
|
|
72
|
+
}: ClientTableProps<T>) => {
|
|
73
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
74
|
+
|
|
75
|
+
const pageCount = Math.ceil(data.length / entriesPerPage);
|
|
76
|
+
|
|
77
|
+
const firstEntry = data.length === 0 ? 0 : (currentPage - 1) * entriesPerPage + 1;
|
|
78
|
+
const lastEntry = Math.min(firstEntry + entriesPerPage - 1, data.length);
|
|
79
|
+
const currentEntries = data.slice(firstEntry - 1, lastEntry);
|
|
80
|
+
const nRows = Math.max(currentEntries.length, minRows ?? -1);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className={className}>
|
|
84
|
+
<div className="rounded-md border bg-card tracking-tight text-muted-foreground shadow">
|
|
85
|
+
<Table>
|
|
86
|
+
<Table.Header>
|
|
87
|
+
<Table.Row>
|
|
88
|
+
{columns.map((column, i) => (
|
|
89
|
+
<Table.Head className="whitespace-nowrap text-foreground md:px-6" key={i}>
|
|
90
|
+
{columnDropdownOptions ? (
|
|
91
|
+
<DropdownMenu>
|
|
92
|
+
<DropdownMenu.Trigger className="flex items-center justify-between gap-3">
|
|
93
|
+
<span>{column.label}</span>
|
|
94
|
+
<ChevronDownIcon />
|
|
95
|
+
</DropdownMenu.Trigger>
|
|
96
|
+
<DropdownMenu.Content align="start">
|
|
97
|
+
<DropdownMenu.Group>
|
|
98
|
+
{columnDropdownOptions.map((option) => {
|
|
99
|
+
const Icon = option.icon;
|
|
100
|
+
return (
|
|
101
|
+
<DropdownMenu.Item
|
|
102
|
+
key={option.label}
|
|
103
|
+
onClick={() => {
|
|
104
|
+
option.onSelection(column);
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
{Icon && <Icon className="mr-2" height={16} width={16} />}
|
|
108
|
+
{option.label}
|
|
109
|
+
</DropdownMenu.Item>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</DropdownMenu.Group>
|
|
113
|
+
</DropdownMenu.Content>
|
|
114
|
+
</DropdownMenu>
|
|
115
|
+
) : (
|
|
116
|
+
column.label
|
|
117
|
+
)}
|
|
118
|
+
</Table.Head>
|
|
119
|
+
))}
|
|
120
|
+
</Table.Row>
|
|
121
|
+
</Table.Header>
|
|
122
|
+
<Table.Body>
|
|
123
|
+
{range(nRows).map((i) => {
|
|
124
|
+
const entry = currentEntries[i];
|
|
125
|
+
return (
|
|
126
|
+
<Table.Row
|
|
127
|
+
className={cn(typeof onEntryClick === 'function' && 'cursor-pointer hover:backdrop-brightness-95')}
|
|
128
|
+
key={i}
|
|
129
|
+
onClick={() => {
|
|
130
|
+
onEntryClick?.(entry);
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
{columns.map(({ field, formatter }, j) => {
|
|
134
|
+
let value: unknown;
|
|
135
|
+
if (!entry) {
|
|
136
|
+
value = '';
|
|
137
|
+
} else if (typeof field === 'function') {
|
|
138
|
+
value = field(entry);
|
|
139
|
+
} else {
|
|
140
|
+
value = entry[field];
|
|
141
|
+
}
|
|
142
|
+
const formattedValue = entry && formatter ? formatter(value) : defaultFormatter(value);
|
|
143
|
+
return (
|
|
144
|
+
<Table.Cell className="text-ellipsis leading-none md:px-6" key={j}>
|
|
145
|
+
{formattedValue}
|
|
146
|
+
</Table.Cell>
|
|
147
|
+
);
|
|
148
|
+
})}
|
|
149
|
+
</Table.Row>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</Table.Body>
|
|
153
|
+
</Table>
|
|
154
|
+
</div>
|
|
155
|
+
<ClientTablePagination
|
|
156
|
+
currentPage={currentPage}
|
|
157
|
+
firstEntry={firstEntry}
|
|
158
|
+
lastEntry={lastEntry}
|
|
159
|
+
pageCount={pageCount}
|
|
160
|
+
setCurrentPage={setCurrentPage}
|
|
161
|
+
totalEntries={data.length}
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../Button/Button.js';
|
|
6
|
+
|
|
7
|
+
export type ClientPagePaginationProps = {
|
|
8
|
+
currentPage: number;
|
|
9
|
+
firstEntry: number;
|
|
10
|
+
lastEntry: number;
|
|
11
|
+
pageCount: number;
|
|
12
|
+
setCurrentPage: (value: number) => void;
|
|
13
|
+
totalEntries: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const ClientTablePagination = ({
|
|
17
|
+
currentPage,
|
|
18
|
+
firstEntry,
|
|
19
|
+
lastEntry,
|
|
20
|
+
pageCount,
|
|
21
|
+
setCurrentPage,
|
|
22
|
+
totalEntries
|
|
23
|
+
}: ClientPagePaginationProps) => {
|
|
24
|
+
const { t } = useTranslation('libui');
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex items-center justify-between py-3">
|
|
27
|
+
<div className="hidden sm:block">
|
|
28
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
29
|
+
{t('pagination.info', {
|
|
30
|
+
first: firstEntry,
|
|
31
|
+
last: lastEntry,
|
|
32
|
+
total: totalEntries
|
|
33
|
+
})}
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="flex flex-1 justify-between gap-3 sm:justify-end">
|
|
37
|
+
<Button
|
|
38
|
+
disabled={currentPage === 1}
|
|
39
|
+
type="button"
|
|
40
|
+
variant="outline"
|
|
41
|
+
onClick={() => {
|
|
42
|
+
setCurrentPage(currentPage - 1);
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{t('pagination.previous')}
|
|
46
|
+
</Button>
|
|
47
|
+
<Button
|
|
48
|
+
disabled={currentPage === pageCount}
|
|
49
|
+
type="button"
|
|
50
|
+
variant="outline"
|
|
51
|
+
onClick={() => {
|
|
52
|
+
setCurrentPage(currentPage + 1);
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{t('pagination.next')}
|
|
56
|
+
</Button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|