From dfcefa690e2a679987520bed609ccac619ca7620 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Tue, 26 Dec 2023 16:54:34 -0800 Subject: [PATCH 1/8] Rm props spread in slider --- src/components/Slider/Slider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Slider/Slider.tsx b/src/components/Slider/Slider.tsx index e3d07ac..925381b 100644 --- a/src/components/Slider/Slider.tsx +++ b/src/components/Slider/Slider.tsx @@ -182,7 +182,6 @@ const RangeSlider: React.FC = (props: SliderProps) => { min={sliderMin} max={sliderMax} step={sliderStep} - {...props} /> From efca0a745482a36b5008b53f05b57e1457f05759 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Wed, 27 Dec 2023 11:01:31 -0800 Subject: [PATCH 2/8] Remove theme from Datatable --- src/components/DataTable/datatable.tsx | 367 +++++++++++-------------- 1 file changed, 168 insertions(+), 199 deletions(-) diff --git a/src/components/DataTable/datatable.tsx b/src/components/DataTable/datatable.tsx index 3a1b897..ac739a7 100644 --- a/src/components/DataTable/datatable.tsx +++ b/src/components/DataTable/datatable.tsx @@ -29,36 +29,8 @@ import Modal from "@mui/material/Modal" import Toolbar from "@mui/material/Toolbar" import Tooltip from "@mui/material/Tooltip" import { styled, alpha } from "@mui/material/styles" -import { createTheme, ThemeProvider } from "@mui/material/styles" import { Stack } from "@mui/material" -const theme = createTheme({ - palette: { - //For accessibility - contrastThreshold: 4.5, - }, - typography: { - fontFamily: "inherit", - }, - shape: { - borderRadius: 4, - }, - components: { - MuiTableCell: { - styleOverrides: { - head: { - fontWeight: "normal", - }, - }, - }, - MuiToolbar: { - styleOverrides: { - - } - } - }, -}) - //Styling for Search component const Search = styled("div")(({ theme }) => ({ position: "relative", @@ -117,7 +89,6 @@ const boxStyle = { p: 4, } -//const RangeSlider: React.FC = (props: SliderProps) => // function DataTable(props: DataTableProps): React.ReactElement> { const DataTable: React.FC> = (props: DataTableProps) => { // Sets default rows to display at 5 if unspecified @@ -239,182 +210,180 @@ const DataTable: React.FC> = (props: DataTableProps) => }, [state.columns, displayedRows]) return ( - - - - - + + + + {props.tableTitle} + {props.titleHoverInfo && ( + + + + )} + + {props.showMoreColumns && props.columns.length > (props.noOfDefaultColumns || 5) && ( + + )} + {props.searchable && ( + + + + + { dispatch({ - type: "modalChanged", - showAddColumnsModal: true, - }) + type: "searchChanged", + value: e.target.value, + }); + setPage(0); } - > - - Manage Columns - - )} - {props.searchable && ( - - - - - { - dispatch({ - type: "searchChanged", - value: e.target.value, - }); + } + /> + + )} + + + + + + {!props.hideHeader && ( + + + {state.columns.map((column, i) => ( + { + !column.unsortable && dispatch({ type: "sortChanged", sortColumn: i }); setPage(0); - } - } - /> - - )} - - - - -
- {!props.hideHeader && ( - - - {state.columns.map((column, i) => ( - { - !column.unsortable && dispatch({ type: "sortChanged", sortColumn: i }); - setPage(0); - }}> - - {column.HeaderRender ? : column.header} - - - - ))} + }}> + + {column.HeaderRender ? : column.header} + + + + ))} + + + )} + + {props.rows.length === 0 ? ( + + {props.emptyText || "No data available."} + {/* Render needed number of empty cells to fill row */} + {handleEmptyTable(props.columns.length)} + + ) : ( + displayedRows.slice(page * rowsPerPage, (page + 1) * rowsPerPage).map((row, i) => ( + props.onRowClick && props.onRowClick(row, i + page * rowsPerPage)} + sx={{ cursor: props.onRowClick ? "pointer" : "auto" }} + onMouseEnter={() => props.onRowMouseEnter && props.onRowMouseEnter(row, i + page * rowsPerPage)} + onMouseLeave={() => props.onRowMouseLeave && props.onRowMouseLeave()} + > + {state.columns.map((column, j) => { + return ( + props.onCellMouseEnter && props.onCellMouseEnter(column.value(row), i, j)} + onMouseLeave={() => props.onCellMouseLeave && props.onCellMouseLeave()} + > + {column.FunctionalRender ? () : column.render ? ( + column.render(row) + ) : ( + column.value(row) + )} + + ) + })} - + )) )} - - {props.rows.length === 0 ? ( - - {props.emptyText || "No data available."} - {/* Render needed number of empty cells to fill row */} - {handleEmptyTable(props.columns.length)} - - ) : ( - displayedRows.slice(page * rowsPerPage, (page + 1) * rowsPerPage).map((row, i) => ( - props.onRowClick && props.onRowClick(row, i + page * rowsPerPage)} - sx={{cursor: props.onRowClick ? "pointer" : "auto"}} - onMouseEnter={() => props.onRowMouseEnter && props.onRowMouseEnter(row, i + page * rowsPerPage)} - onMouseLeave={() => props.onRowMouseLeave && props.onRowMouseLeave()} - > - {state.columns.map((column, j) => { - return ( - props.onCellMouseEnter && props.onCellMouseEnter(column.value(row), i, j)} - onMouseLeave={() => props.onCellMouseLeave && props.onCellMouseLeave()} - > - {column.FunctionalRender ? () : column.render ? ( - column.render(row) - ) : ( - column.value(row) - )} - - ) - })} - - )) - )} - -
-
- {!props.hidePageMenu && ( - - - {displayedRows.length !== props.rows.length && `Showing ${displayedRows.length} matching rows of ${props.rows.length} total.`} - - - - )} - {/* Add columns modal */} - dispatch({ type: "modalChanged", showAddColumnsModal: false })}> - - Add Columns - {(props.defaultColumnsToShow - ? props.columns.filter((c) => !props.defaultColumnsToShow?.includes(c.header)) - //Why is this "or 5" here? Kinda makes no sense? - : props.columns.slice(props.noOfDefaultColumns || 5, props.columns.length) - ).map((col, i) => ( - - c.header === col.header) !== undefined} - onChange={(event) => { - if (event.target.checked && props.columns.find((c) => c.header === col.header)) - dispatch({ - type: "columnsChanged", - columns: [...state.columns, col], - }) - else - dispatch({ - type: "columnsChanged", - columns: state.columns.filter((u) => u.header !== col.header), - }) - }} - /> - } - label={col.header} - /> -
-
- ))} - -
-
-
-
+ + + + {!props.hidePageMenu && ( + + + {displayedRows.length !== props.rows.length && `Showing ${displayedRows.length} matching rows of ${props.rows.length} total.`} + + + + )} + {/* Add columns modal */} + dispatch({ type: "modalChanged", showAddColumnsModal: false })}> + + Add Columns + {(props.defaultColumnsToShow + ? props.columns.filter((c) => !props.defaultColumnsToShow?.includes(c.header)) + //Why is this "or 5" here? Kinda makes no sense? + : props.columns.slice(props.noOfDefaultColumns || 5, props.columns.length) + ).map((col, i) => ( + + c.header === col.header) !== undefined} + onChange={(event) => { + if (event.target.checked && props.columns.find((c) => c.header === col.header)) + dispatch({ + type: "columnsChanged", + columns: [...state.columns, col], + }) + else + dispatch({ + type: "columnsChanged", + columns: state.columns.filter((u) => u.header !== col.header), + }) + }} + /> + } + label={col.header} + /> +
+
+ ))} + +
+
+ ) } export default DataTable From 7a65ea93af6c599c6ae61345bdcda3c26a52f0a4 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Wed, 27 Dec 2023 11:08:10 -0800 Subject: [PATCH 3/8] Thin slider rail --- package.json | 2 +- src/components/Slider/Slider.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6826366..cf16640 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": "0.8.0", + "version": "0.8.1", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/components/Slider/Slider.tsx b/src/components/Slider/Slider.tsx index 925381b..9ed79a3 100644 --- a/src/components/Slider/Slider.tsx +++ b/src/components/Slider/Slider.tsx @@ -182,6 +182,7 @@ const RangeSlider: React.FC = (props: SliderProps) => { min={sliderMin} max={sliderMax} step={sliderStep} + sx={{height: 2}} /> From 2bfaa307dd301039927e35857c2b3bd94068beb7 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Wed, 27 Dec 2023 12:13:47 -0800 Subject: [PATCH 4/8] Rm pr except on last, hide sort icon when inactive --- src/components/DataTable/datatable.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/DataTable/datatable.tsx b/src/components/DataTable/datatable.tsx index ac739a7..c686ae1 100644 --- a/src/components/DataTable/datatable.tsx +++ b/src/components/DataTable/datatable.tsx @@ -273,13 +273,17 @@ const DataTable: React.FC> = (props: DataTableProps) => {state.columns.map((column, i) => ( - { + //remove padding from right unless last column + { !column.unsortable && dispatch({ type: "sortChanged", sortColumn: i }); setPage(0); }}> - + {column.HeaderRender ? : column.header} - ))} @@ -308,7 +312,8 @@ const DataTable: React.FC> = (props: DataTableProps) => {state.columns.map((column, j) => { return ( props.onCellMouseEnter && props.onCellMouseEnter(column.value(row), i, j)} onMouseLeave={() => props.onCellMouseLeave && props.onCellMouseLeave()} From 3a729f49881764e7aafe3ac64b47c97f4f25e609 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Wed, 27 Dec 2023 13:00:33 -0800 Subject: [PATCH 5/8] Move title/search outside of scroll --- src/components/DataTable/datatable.tsx | 112 ++++++++++++------------- stories/DataTable.stories.tsx | 14 +++- 2 files changed, 68 insertions(+), 58 deletions(-) diff --git a/src/components/DataTable/datatable.tsx b/src/components/DataTable/datatable.tsx index c686ae1..05b6083 100644 --- a/src/components/DataTable/datatable.tsx +++ b/src/components/DataTable/datatable.tsx @@ -210,71 +210,69 @@ const DataTable: React.FC> = (props: DataTableProps) => }, [state.columns, displayedRows]) return ( - - - - + + + {props.tableTitle} + {props.titleHoverInfo && ( + + + + )} + + {props.showMoreColumns && props.columns.length > (props.noOfDefaultColumns || 5) && ( + + )} + {props.searchable && ( + + + + + { dispatch({ - type: "modalChanged", - showAddColumnsModal: true, - }) + type: "searchChanged", + value: e.target.value, + }); + setPage(0); } - > - - Manage Columns - - )} - {props.searchable && ( - - - - - { - dispatch({ - type: "searchChanged", - value: e.target.value, - }); - setPage(0); - } - } - /> - - )} - - - - + } + /> + + )} + + + + + {!props.hideHeader && ( {state.columns.map((column, i) => ( //remove padding from right unless last column - { + { !column.unsortable && dispatch({ type: "sortChanged", sortColumn: i }); setPage(0); }}> @@ -313,7 +311,7 @@ const DataTable: React.FC> = (props: DataTableProps) => return ( props.onCellMouseEnter && props.onCellMouseEnter(column.value(row), i, j)} onMouseLeave={() => props.onCellMouseLeave && props.onCellMouseLeave()} diff --git a/stories/DataTable.stories.tsx b/stories/DataTable.stories.tsx index fc3dd6f..e6914ca 100644 --- a/stories/DataTable.stories.tsx +++ b/stories/DataTable.stories.tsx @@ -57,7 +57,7 @@ const COLUMNS: DataTableColumn[] = [{ }]; const ROWS = [ - { index: 0, text: "this is row 0", color: "#ff0000", description: "this is row 0" }, + { index: 0, text: "this_is_row_0", color: "#ff0000", description: "this_is_row_0" }, { index: 1, text: "this is row 1", color: "#dd0000", description: "this is row 1" }, { index: 2, text: "this is row 2", color: "#bb0000", description: "this is row 2" }, { index: 3, text: "this is row 3", color: "#990000", description: "this is row 3" }, @@ -719,6 +719,18 @@ const denseCols = [{ ); } + export const LotsOfCols = (props?: Partial>) => { + return ( + + ); + } + const headeRenderCOLUMNS = (func: any) => { return( [{ header: "Index", From 5c00ff1f34e96889f603b851817210c31d4d5526 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Thu, 28 Dec 2023 12:13:11 -0800 Subject: [PATCH 6/8] Add scroll buttons when content overflows --- src/components/DataTable/datatable.tsx | 68 ++++++++++++++++++++++++-- stories/DataTable.stories.tsx | 2 +- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/components/DataTable/datatable.tsx b/src/components/DataTable/datatable.tsx index 05b6083..c8bc1b8 100644 --- a/src/components/DataTable/datatable.tsx +++ b/src/components/DataTable/datatable.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useReducer, Reducer, useMemo, Fragment, useState } from "react" +import React, { useCallback, useReducer, Reducer, useMemo, Fragment, useState, useEffect, useRef } from "react" // import * as os from "os" import { genericSort } from "../utilities" import { DataTableProps, DataTableState, DataTableAction } from "./types" @@ -30,6 +30,8 @@ import Toolbar from "@mui/material/Toolbar" import Tooltip from "@mui/material/Tooltip" import { styled, alpha } from "@mui/material/styles" import { Stack } from "@mui/material" +import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; +import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; //Styling for Search component const Search = styled("div")(({ theme }) => ({ @@ -209,8 +211,40 @@ const DataTable: React.FC> = (props: DataTableProps) => a.remove() }, [state.columns, displayedRows]) + //Refs used in tracking overflow + const containerRef = useRef(null) + const arrowRightRef = useRef(null) + const arrowLeftRef = useRef(null) + + //Shows/hides scroll indicators based on refs + const monitorOverflow = ( + containerRef: React.RefObject, + arrowRightRef: React.RefObject, + arrowLeftRef: React.RefObject + ) => { + const isOverflowingLeft = containerRef.current && containerRef.current.scrollLeft > 0 + const isOverflowingRight = containerRef.current && (containerRef.current.scrollLeft + 1 < containerRef.current.scrollWidth - containerRef.current.clientWidth); + const isOverflowing = containerRef.current && containerRef.current.scrollWidth > containerRef.current.clientWidth + if (arrowRightRef.current) arrowRightRef.current.style.visibility = isOverflowing && isOverflowingRight ? "visible" : "hidden"; + if (arrowLeftRef.current) arrowLeftRef.current.style.visibility = isOverflowing && isOverflowingLeft ? "visible" : "hidden"; + } + + //Attaches scroll and resize listeners to scrollable container + useEffect(() => { + if (containerRef.current !== null) { + containerRef.current.addEventListener('scroll', () => monitorOverflow(containerRef, arrowRightRef, arrowLeftRef)) + + new ResizeObserver((entries) => { + for (const _ of entries) { + monitorOverflow(containerRef, arrowRightRef, arrowLeftRef) + } + }).observe(containerRef.current) + } + }, [containerRef, arrowLeftRef, arrowRightRef]) + + return ( - + > = (props: DataTableProps) => - +
{!props.hideHeader && ( @@ -329,6 +363,34 @@ const DataTable: React.FC> = (props: DataTableProps) => )}
+ {if (containerRef.current) containerRef.current.scrollLeft = 0}} + > + + + {if (containerRef.current) containerRef.current.scrollLeft = containerRef.current.scrollWidth - containerRef.current.clientWidth}} + > + +
{!props.hidePageMenu && ( diff --git a/stories/DataTable.stories.tsx b/stories/DataTable.stories.tsx index e6914ca..7f63051 100644 --- a/stories/DataTable.stories.tsx +++ b/stories/DataTable.stories.tsx @@ -724,7 +724,7 @@ const denseCols = [{ From dd5d63732f2c77798ce434f611bc19f01353fb74 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Thu, 28 Dec 2023 12:28:57 -0800 Subject: [PATCH 7/8] Fix maxHeight behavior --- src/components/DataTable/datatable.tsx | 4 ++-- src/components/DataTable/types.ts | 2 +- stories/DataTable.stories.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/DataTable/datatable.tsx b/src/components/DataTable/datatable.tsx index c8bc1b8..d467d36 100644 --- a/src/components/DataTable/datatable.tsx +++ b/src/components/DataTable/datatable.tsx @@ -244,7 +244,7 @@ const DataTable: React.FC> = (props: DataTableProps) => return ( - + > = (props: DataTableProps) => - + {!props.hideHeader && ( diff --git a/src/components/DataTable/types.ts b/src/components/DataTable/types.ts index 0e98577..ed0f066 100644 --- a/src/components/DataTable/types.ts +++ b/src/components/DataTable/types.ts @@ -38,7 +38,7 @@ export type DataTableProps = { headerColor?: {backgroundColor: RGB | RGBA | HEX , textColor: RGB | RGBA | HEX | 'inherit'}; /** - * Note: This currently does not account for the size of the pagination element, + * Note: This currently does not account for the size of the title or pagination element, * sets max-height of the table header and body. */ maxHeight?: number | string diff --git a/stories/DataTable.stories.tsx b/stories/DataTable.stories.tsx index 7f63051..8302a38 100644 --- a/stories/DataTable.stories.tsx +++ b/stories/DataTable.stories.tsx @@ -172,10 +172,10 @@ HeaderColored.args = { ConstrainSize.args = { rows: ROWS, columns: COLUMNS, - itemsPerPage: 4, + itemsPerPage: 8, tableTitle: "Table Title", searchable: true, - maxHeight: '350px', + maxHeight: '300px', } DensePadding.args = { From ee709e1ed03117c53933606b7e3a0de8d0e703d5 Mon Sep 17 00:00:00 2001 From: Jonathan Fisher Date: Thu, 28 Dec 2023 12:37:48 -0800 Subject: [PATCH 8/8] Cleanup --- src/components/DataTable/datatable.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/DataTable/datatable.tsx b/src/components/DataTable/datatable.tsx index d467d36..d2fdbb1 100644 --- a/src/components/DataTable/datatable.tsx +++ b/src/components/DataTable/datatable.tsx @@ -222,11 +222,13 @@ const DataTable: React.FC> = (props: DataTableProps) => arrowRightRef: React.RefObject, arrowLeftRef: React.RefObject ) => { - const isOverflowingLeft = containerRef.current && containerRef.current.scrollLeft > 0 - const isOverflowingRight = containerRef.current && (containerRef.current.scrollLeft + 1 < containerRef.current.scrollWidth - containerRef.current.clientWidth); - const isOverflowing = containerRef.current && containerRef.current.scrollWidth > containerRef.current.clientWidth - if (arrowRightRef.current) arrowRightRef.current.style.visibility = isOverflowing && isOverflowingRight ? "visible" : "hidden"; - if (arrowLeftRef.current) arrowLeftRef.current.style.visibility = isOverflowing && isOverflowingLeft ? "visible" : "hidden"; + if (containerRef.current && arrowRightRef.current && arrowLeftRef.current) { + const isOverflowing = containerRef.current.scrollWidth > containerRef.current.clientWidth + const isOverflowingLeft = containerRef.current.scrollLeft > 0 + const isOverflowingRight = containerRef.current.scrollLeft + 1 < containerRef.current.scrollWidth - containerRef.current.clientWidth + arrowRightRef.current.style.visibility = isOverflowing && isOverflowingRight ? "visible" : "hidden" + arrowLeftRef.current.style.visibility = isOverflowing && isOverflowingLeft ? "visible" : "hidden" + } } //Attaches scroll and resize listeners to scrollable container