diff --git a/eslint.config.js b/eslint.config.js
index 092408a..1adb816 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -25,4 +25,10 @@ export default tseslint.config(
],
},
},
+ {
+ files: ["**/*.stories.tsx"],
+ rules: {
+ "react-hooks/rules-of-hooks": "off"
+ }
+ }
)
diff --git a/package-lock.json b/package-lock.json
index 81a8346..612d68f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@weng-lab/psychscreen-ui-components",
- "version": "2.0.4",
+ "version": "2.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@weng-lab/psychscreen-ui-components",
- "version": "2.0.4",
+ "version": "2.0.5",
"license": "MIT",
"dependencies": {
"cytoscape": "^3.30.2",
diff --git a/package.json b/package.json
index 14507f3..3e90cf2 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "@weng-lab/psychscreen-ui-components",
"description": "Typescript and Material UI based components used for psychSCREEN",
"author": "SCREEN Team @ UMass Chan Medical School",
- "version": "2.0.5",
+ "version": "2.0.6",
"license": "MIT",
"type": "module",
"typings": "dist/index.d.ts",
@@ -75,5 +75,6 @@
"extends": [
"plugin:storybook/recommended"
]
- }
+ },
+ "packageManager": "yarn@3.5.0+sha512.2dc70be5fce9f66756d25b00a888f3ca66f86b502b76750e72ba54cec89da767b938c54124595e26f868825688e0fe3552c26c76a330673343057acadd5cfcf2"
}
diff --git a/src/components/DataTable/DataTable.stories.tsx b/src/components/DataTable/DataTable.stories.tsx
index 6c614d2..3acd1cd 100644
--- a/src/components/DataTable/DataTable.stories.tsx
+++ b/src/components/DataTable/DataTable.stories.tsx
@@ -227,11 +227,10 @@ export const FunctionalComponentColumn: Story = {
args: {
rows: ROWS,
columns: FCCOLUMNS,
- itemsPerPage: 4,
tableTitle: 'Table Title',
searchable: true,
showMoreColumns: true,
- noOfDefaultColumns: 3,
+ noOfDefaultColumns: 5,
}
}
@@ -295,6 +294,23 @@ export const HeaderColored: Story = {
}
}
+export const ItemsPerPage: Story = {
+ args: {
+ rows: ROWS,
+ columns: COLUMNS,
+ tableTitle: "Table Title"
+ },
+ render: (args) =>
+
+ Default
+
+ itemsPerPage = 5
+
+ itemsPerPage = [3,5,10]
+
+
+}
+
export const ConstrainSize: Story = {
args: {
rows: ROWS,
@@ -432,7 +448,7 @@ export const LotsOfCols: Story = {
}
}
-const headeRenderCOLUMNS = (func: any) => {
+const headeRenderCOLUMNS = (setX: React.Dispatch>) => {
return [
{
header: 'Index',
@@ -488,7 +504,7 @@ const headeRenderCOLUMNS = (func: any) => {
switch (value) {
case 0:
setDistanceChecked(event.target.checked);
- func(event.target.checked);
+ setX(event.target.checked);
break;
case 1:
setCTCF_ChIAPETChecked(event.target.checked);
@@ -570,7 +586,7 @@ export const HeaderRender: Story = {
searchable: true
},
render: (args) => {
- const [x, setX] = React.useState(null);
+ const [x, setX] = React.useState(null);
useEffect(() => console.log(x));
return (
({
//Styling for "Add Columns" Modal
const boxStyle = {
- position: 'absolute' as 'absolute',
+ position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
@@ -104,10 +104,12 @@ const boxStyle = {
const DataTable = (
props: DataTableProps
) => {
- // Sets default rows to display at 5 if unspecified
- const itemsPerPage = props.itemsPerPage || 5;
const [page, setPage] = useState(props.page || 0);
- const [rowsPerPage, setRowsPerPage] = useState(itemsPerPage);
+ const [rowsPerPage, setRowsPerPage] = useState(() => {
+ if (Array.isArray(props.itemsPerPage)) { return props.itemsPerPage[0] }
+ else if (typeof props.itemsPerPage == "number") { return props.itemsPerPage }
+ else return 5
+ })
const handleChangePage = (page: number) => {
setPage(page);
@@ -120,8 +122,8 @@ const DataTable = (
setPage(0);
};
- function handleEmptyTable(noColumns: number): React.JSX.Element[] {
- let cells = [];
+ const handleSpawnEmptyCells = (noColumns: number): React.JSX.Element[] => {
+ const cells = [];
for (let i = 1; i < noColumns; i++) {
cells.push();
}
@@ -129,7 +131,7 @@ const DataTable = (
}
function highlightCheck(row: T): boolean {
- var found = false;
+ let found = false;
if (Array.isArray(props.highlighted)) {
props.highlighted.forEach((highlight) => {
if (JSON.stringify(row) === JSON.stringify(highlight)) {
@@ -169,7 +171,7 @@ const DataTable = (
});
const search = useCallback(
- (row: any, value: string): boolean => {
+ (row: T, value: string): boolean => {
/* look for any matching searchable column */
for (const i in state.columns) {
/* get column; look for a user-defined search function first */
@@ -207,7 +209,7 @@ const DataTable = (
);
const displayRows = useCallback(
- (sortedRows: any[], filterValue: string): any[] =>
+ (sortedRows: T[], filterValue: string): T[] =>
filterValue === ''
? [...sortedRows]
: sortedRows.filter((row) => search(row, filterValue)),
@@ -216,21 +218,24 @@ const DataTable = (
const displayedRows = useMemo(
() => sort(displayRows(props.rows, state.filter || props.search || '')),
- [displayRows, sort, state.filter, props.rows, state.sort, props.search]
+ [displayRows, sort, state.filter, props.rows, props.search]
);
const rowsOnCurrentPage = useMemo(() => {
const newRowsOnPage = displayedRows.slice(page * rowsPerPage, (page + 1) * rowsPerPage);
props.onDisplayedRowsChange?.(page, newRowsOnPage)
return newRowsOnPage
- }, [displayedRows, page, rowsPerPage])
+ }, [displayedRows, page, rowsPerPage, props.onDisplayedRowsChange])
+
+ // Avoid a layout jump when reaching the last page with empty rows.
+ const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - props.rows.length) : 0;
const download = useCallback(() => {
const data =
state.columns.map((col) => col.header).join('\t') +
'\n' +
displayedRows
- .map((row: any) =>
+ .map((row: T) =>
state.columns.map((col) => col.value(row)).join('\t')
)
.join('\n') +
@@ -245,7 +250,8 @@ const DataTable = (
a.click();
window.URL.revokeObjectURL(url);
a.remove();
- }, [state.columns, displayedRows]);
+ }, [state.columns, displayedRows, props.downloadFileName]);
+
//Refs used in tracking overflow
const containerRef = useRef(null);
@@ -279,14 +285,14 @@ const DataTable = (
monitorOverflow(containerRef, arrowRightRef, arrowLeftRef)
);
- new ResizeObserver((entries) => {
- for (const _ of entries) {
- monitorOverflow(containerRef, arrowRightRef, arrowLeftRef);
- }
+ new ResizeObserver(() => {
+ monitorOverflow(containerRef, arrowRightRef, arrowLeftRef);
}).observe(containerRef.current);
}
}, [containerRef, arrowLeftRef, arrowRightRef]);
+ console.log(document.getElementById('row0')?.offsetHeight)
+
return (
((
sx={i !== state.columns.length - 1 ? { pr: 0 } : {}}
key={`${column.header}${i}`}
onClick={() => {
- !column.unsortable &&
+ if (!column.unsortable) {
dispatch({ type: 'sortChanged', sortColumn: i });
+ }
setPage(0);
}}
>
@@ -425,16 +432,18 @@ const DataTable = (
{props.emptyText || 'No data available.'}
{/* Render needed number of empty cells to fill row */}
- {handleEmptyTable(props.columns.length)}
+ {handleSpawnEmptyCells(props.columns.length)}
) : (
- rowsOnCurrentPage
+ <>
+ {rowsOnCurrentPage
.map((row, i) => (
props.onRowClick &&
props.onRowClick(row, i + page * rowsPerPage)
@@ -463,7 +472,7 @@ const DataTable = (
}
>
{column.FunctionalRender ? (
-
+
) : column.render ? (
column.render(row)
) : (
@@ -473,7 +482,17 @@ const DataTable = (
);
})}
- ))
+ ))}
+ {emptyRows > 0 && (
+
+
+
+ )}
+ >
)}
@@ -521,7 +540,7 @@ const DataTable = (
`Showing ${displayedRows.length} matching rows of ${props.rows.length} total.`}
(
sx={
props.dense
? {
- '& .MuiTablePagination-toolbar': { pl: '6px' },
- '& .css-h0cf5v-MuiInputBase-root-MuiTablePagination-select':
- { mr: '6px', ml: '0px' },
- '& .MuiTablePagination-actions': { ml: '4px !important' },
- }
+ '& .MuiTablePagination-toolbar': { pl: '6px' },
+ '& .css-h0cf5v-MuiInputBase-root-MuiTablePagination-select':
+ { mr: '6px', ml: '0px' },
+ '& .MuiTablePagination-actions': { ml: '4px !important' },
+ }
: undefined
}
/>
diff --git a/src/components/DataTable/types.ts b/src/components/DataTable/types.ts
index ad8ea5a..261d8b9 100644
--- a/src/components/DataTable/types.ts
+++ b/src/components/DataTable/types.ts
@@ -3,12 +3,12 @@ import React from "react"
export type DataTableColumn = {
tooltip?: string
header: string
- HeaderRender?: React.FC
+ HeaderRender?: React.FC
value: (row: T) => string | number
search?: (row: T) => boolean
unsearchable?: boolean
render?: (row: T) => string | JSX.Element
- FunctionalRender?: React.FC
+ FunctionalRender?: (props: { row: T }) => JSX.Element;
sort?: (a: T, b: T) => number
unsortable?: boolean
}
@@ -20,7 +20,18 @@ type HEX = `#${string}` | `# ${string}`;
export type DataTableProps = {
columns: DataTableColumn[]
rows: T[]
- itemsPerPage?: number
+
+ /**
+ * Sets the number of items on each page.
+ * If one number specified, the rows per page selection is hidden.
+ * Specify an array to provide user-selectable options
+ *
+ * @default
+ * [5, 10, 25, 100]
+ *
+ */
+ itemsPerPage?: number | number[]
+
hidePageMenu?: boolean
tableTitle?: string
selectable?: boolean