-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
083136e
commit 62fa389
Showing
6 changed files
with
397 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
import { styled, alpha, darken, lighten } from "@mui/material/styles"; | ||
import { | ||
DataGrid as MuiDataGrid, | ||
GridToolbar, | ||
type DataGridProps as MuiDataGridProps, | ||
} from "@mui/x-data-grid"; | ||
import { avatarClassNames } from "@components/Avatar"; | ||
import { dataGridClassNames } from "./classNames"; | ||
import { gridPanelSX } from "./styles.gridPanel"; | ||
import { dataGridStyledPrintMedia } from "./styles.printMedia"; | ||
import type { OverrideProperties } from "type-fest"; | ||
|
||
/** | ||
* Styled MUI DataGrid with `GridToolbar` | ||
* | ||
* - Column and row buffers have been increased from 3 (default) to 6 for a | ||
* smoother scrolling experience; smaller buffers increase the likelihood a | ||
* user will see a delayed "pop-in" as the virt-list renders when scrolling. | ||
* | ||
* - By default, CSV export files will have prefixes indicating UTF-8 BOM | ||
* (Byte Order Mark), which can/should allow Excel to automatically detect | ||
* the file encoding as UTF-8. | ||
* | ||
* - A "panel" renders as a Mui-Popper when a toolbar button like "COLUMNS" is | ||
* clicked/pressed; since it renders in a portal outside of the DataGrid's | ||
* position in the DOM tree, styles can't be applied via css selectors within | ||
* StyledMuiDataGrid, so its styles/sx are provided inline. | ||
*/ | ||
export const DataGrid = ({ | ||
slots = {}, | ||
slotProps = {}, | ||
columnBuffer = 6, | ||
rowBuffer = 6, | ||
...props | ||
}: DataGridProps) => ( | ||
<StyledMuiDataGrid | ||
slots={{ toolbar: GridToolbar, ...slots }} | ||
slotProps={{ | ||
...slotProps, | ||
|
||
toolbar: { | ||
...(slotProps?.toolbar ?? {}), | ||
printOptions: { | ||
hideFooter: true, | ||
hideToolbar: true, | ||
...(slotProps?.toolbar?.printOptions ?? {}), | ||
}, | ||
csvOptions: { | ||
utf8WithBom: true, | ||
...(slotProps?.toolbar?.csvOptions ?? {}), | ||
}, | ||
}, | ||
|
||
panel: { | ||
...(slotProps?.panel ?? {}), | ||
sx: { | ||
...gridPanelSX, | ||
...(slotProps?.panel?.sx ?? {}), | ||
}, | ||
}, | ||
}} | ||
columnBuffer={columnBuffer} | ||
rowBuffer={rowBuffer} | ||
getRowClassName={({ indexRelativeToCurrentPage: rowIndex }) => | ||
rowIndex % 2 === 0 ? dataGridClassNames.rowIndexEven : dataGridClassNames.rowIndexOdd | ||
} | ||
{...props} | ||
/> | ||
); | ||
|
||
const StyledMuiDataGrid = styled(MuiDataGrid)(({ theme: { palette, variables } }) => { | ||
const rowHoverStyle = { | ||
opacity: 0.85, | ||
backgroundColor: | ||
palette.mode === "dark" | ||
? lighten(palette.background.paper, 0.1) | ||
: darken(palette.background.paper, 0.1), | ||
}; | ||
|
||
return { | ||
height: "100%", | ||
width: "100%", | ||
backgroundColor: palette.background.paper, | ||
borderStyle: "solid", | ||
...(palette.mode === "light" && { | ||
borderColor: palette.divider, | ||
}), | ||
|
||
// @media print | ||
...dataGridStyledPrintMedia, | ||
|
||
// TOOLBAR | ||
[`& .${dataGridClassNames.toolbarContainer}`]: { | ||
padding: "0.5rem", | ||
alignItems: "stretch", // make all btns same height as toolbar | ||
justifyContent: variables.isMobilePageLayout ? "space-between" : "flex-end", | ||
gap: "0.5rem", | ||
borderStyle: "solid", | ||
borderColor: palette.divider, | ||
borderWidth: "0 0 1px 0", | ||
// Toolbar buttons | ||
"& button": { | ||
...(variables.isMobilePageLayout && { | ||
width: "20%", | ||
fontSize: "0.65rem", | ||
fontWeight: "normal", | ||
padding: "0.25rem", | ||
}), | ||
color: palette.secondary.main, | ||
backgroundColor: palette.background.paper, | ||
borderWidth: variables.isMobilePageLayout ? 0 : "1px", | ||
borderStyle: "solid", | ||
borderColor: palette.secondary.main, | ||
verticalAlign: "middle", | ||
"& .MuiButton-startIcon": { | ||
display: "flex", | ||
alignItems: "center", | ||
alignSelf: "center", | ||
...(variables.isMobilePageLayout && { | ||
maxWidth: "1rem", | ||
margin: "0 3px 0 0", | ||
paddingTop: "1px", | ||
"& svg": { | ||
maxWidth: "1rem", | ||
}, | ||
}), | ||
}, | ||
"&:hover": { | ||
opacity: 0.6, | ||
}, | ||
"& svg:first-of-type": { | ||
transform: "translateY(-2px)", | ||
}, | ||
}, | ||
// For some reason, Mui places a div at the end that's too big, get rid of it | ||
"& > div:last-of-type": { display: "none" }, | ||
}, | ||
|
||
// COLUMN GROUP HEADERS | ||
[`& .${dataGridClassNames.columnHeadersInner} > div:not(:last-of-type)`]: { | ||
[`& .${dataGridClassNames.columnHeader}`]: { | ||
padding: 0, | ||
|
||
[`& div.${dataGridClassNames.columnHeaderTitleContainer}`]: { | ||
paddingTop: "0.75rem", | ||
lineHeight: "2rem", | ||
alignItems: "flex-end", | ||
justifyContent: "center", | ||
}, | ||
}, | ||
}, | ||
|
||
// ROWS CONTAINER | ||
[`& .${dataGridClassNames.virtualScroller}`]: { | ||
backgroundColor: palette.background.default, | ||
"&:hover": { cursor: "pointer" }, | ||
|
||
// ROWS | ||
[`& .${dataGridClassNames.row}`]: { | ||
"&:hover": rowHoverStyle, | ||
}, | ||
// Banded rows by using custom className: | ||
[`& .${dataGridClassNames.row}.${dataGridClassNames.rowIndexEven}`]: { | ||
backgroundColor: darken(palette.background.paper, palette.mode === "dark" ? 0.1 : 0.025), | ||
"&:hover": rowHoverStyle, | ||
}, | ||
|
||
// CELLS | ||
[`& .${dataGridClassNames.cell}`]: { | ||
fontSize: "0.875rem", | ||
lineHeight: "0.875rem", | ||
padding: "0.75rem 0.625rem 0.5rem 0.625rem", | ||
verticalAlign: "middle", | ||
borderWidth: "1px 1px 0 0", | ||
borderStyle: "solid", | ||
borderColor: | ||
palette.mode === "dark" | ||
? alpha(palette.grey[300], 0.125) | ||
: alpha(palette.grey[400], 0.75), | ||
[`& > .${dataGridClassNames.cellContent}`]: { | ||
verticalAlign: "middle", | ||
}, | ||
|
||
// Avatar comps in cells (using our custom className): | ||
[`& .${avatarClassNames.root}`]: { | ||
"& .MuiTypography-root": { | ||
fontSize: "0.875rem", // TODO this will effect initial-char avatars AND displayName - is that ok? | ||
}, | ||
}, | ||
}, | ||
[`& .${dataGridClassNames.cell}:not(:last-of-type)`]: { | ||
borderRightColor: | ||
palette.mode === "dark" | ||
? alpha(palette.grey[300], 0.035) | ||
: alpha(palette.grey[400], 0.45), | ||
}, | ||
}, | ||
|
||
// NO-ROWS-OVERLAY | ||
[`& .${dataGridClassNames.overlay}`]: { | ||
textTransform: "capitalize", | ||
}, | ||
}; | ||
}); | ||
|
||
export type DataGridProps = OverrideProperties< | ||
Omit<React.ComponentProps<typeof StyledMuiDataGrid>, "getRowClassName">, | ||
{ slots?: Omit<NonNullable<MuiDataGridProps["slots"]>, "toolbar"> } | ||
>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { styled } from "@mui/material/styles"; | ||
import Text from "@mui/material/Typography"; | ||
import { EmptyListFallback, type EmptyListFallbackProps } from "@components/HelpInfo"; | ||
|
||
/** | ||
* An `EmptyListFallback` component for DataGrid `noRowsOverlay` slots | ||
* which provides an aesthetically refined and consistent fallback UI | ||
* for empty DataGrids. | ||
*/ | ||
export const EmptyDataGridFallback = ({ | ||
nameOfMissingItems, | ||
children, | ||
...props | ||
}: EmptyDataGridFallbackProps) => ( | ||
<EmptyListFallback | ||
text={`No ${nameOfMissingItems} Available`} | ||
tooltip={ | ||
// prettier-ignore | ||
<StyledText> | ||
This <b>super-charged</b> data table makes it easy to manage your {nameOfMissingItems}! | ||
</StyledText> | ||
} | ||
{...props} | ||
> | ||
{children} | ||
</EmptyListFallback> | ||
); | ||
|
||
const StyledText = styled(Text)(({ theme: { palette } }) => ({ | ||
// fontSize: "0.95rem", // TODO what's the default tooltip fontSize? (a bit too small maybe) | ||
lineHeight: "1.25rem", | ||
|
||
"& > b": { | ||
color: palette.mode === "dark" ? palette.primary.main : palette.primary.light, | ||
fontStyle: "italic", | ||
}, | ||
})); | ||
|
||
/** | ||
* @type EmptyDataGridFallbackProps | ||
* @property nameOfMissingItems - The name of the missing items, e.g. `"Invoices"` | ||
*/ | ||
export type EmptyDataGridFallbackProps = { | ||
nameOfMissingItems: string; | ||
} & Omit<EmptyListFallbackProps, "text" | "tooltip">; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { gridClasses } from "@mui/x-data-grid"; | ||
|
||
export const dataGridClassNames = { | ||
...gridClasses, | ||
rowIndexEven: "data-grid-row-index-even", | ||
rowIndexOdd: "data-grid-row-index-odd", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./DataGrid"; | ||
export * from "./classNames"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { buttonBaseClasses } from "@mui/material/ButtonBase"; | ||
import { formControlLabelClasses } from "@mui/material/FormControlLabel"; | ||
import { paperClasses } from "@mui/material/Paper"; | ||
import { switchClasses } from "@mui/material/Switch"; | ||
import { dataGridClassNames as classNames } from "./classNames"; | ||
|
||
/** | ||
* Custom styles for the DataGrid Panel `sx` prop. | ||
*/ | ||
export const gridPanelSX = { | ||
[`& .${paperClasses.root}`]: { | ||
borderWidth: "0 1px 1px 1px", | ||
borderStyle: "solid", | ||
borderColor: "rgba(255, 255, 255, 0.35)", | ||
borderRadius: "0 0 0.25rem 0.25rem", | ||
|
||
// PANEL HEADER: | ||
[`& .${classNames.panelHeader}`]: { | ||
borderBottomWidth: "1px", | ||
borderBottomStyle: "solid", | ||
borderBottomColor: "divider", | ||
boxShadow: "0 3px 2px 0 rgba(0,0,0,0.3)", | ||
}, | ||
|
||
// PANEL CONTENT: | ||
[`& .${classNames.panelContent}`]: { | ||
// COLUMNS PANEL: | ||
[`& .${classNames.columnsPanelRow}`]: { | ||
"&:not(:last-of-type)": { | ||
borderBottomWidth: "1px", | ||
borderBottomStyle: "solid", | ||
borderBottomColor: "divider", | ||
}, | ||
[`& > .${formControlLabelClasses.root}`]: { | ||
marginLeft: "-0.5rem", | ||
[`& > .${switchClasses.root}`]: { | ||
marginRight: "0.5rem", | ||
[`& > .${switchClasses.switchBase}.${switchClasses.checked}`]: { | ||
color: "secondary.main", | ||
[`& + .${switchClasses.track}`]: { | ||
backgroundColor: "secondary.dark", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
|
||
// FILTER-FORM PANEL: | ||
|
||
[`& .${classNames.filterForm}`]: { | ||
[`& > .${classNames.filterFormColumnInput}`]: { | ||
marginLeft: "0.25rem", | ||
}, | ||
[`& > .${classNames.filterFormOperatorInput},.${classNames.filterFormValueInput}`]: { | ||
marginLeft: "1rem", | ||
}, | ||
}, | ||
}, | ||
|
||
// PANEL FOOTER: | ||
[`& .${classNames.panelFooter}`]: { | ||
borderTopWidth: "1px", | ||
borderTopStyle: "solid", | ||
borderTopColor: "divider", | ||
boxShadow: "0 -3px 2px 0 rgba(0,0,0,0.3)", | ||
padding: "0.5rem 0", | ||
justifyContent: "space-evenly", | ||
|
||
// "SHOW ALL" and "HIDE ALL" buttons in columns-panel footer: | ||
[`& > .${buttonBaseClasses.root}`]: { | ||
backgroundColor: "action.selected", | ||
color: "text.primary", | ||
fontSize: "0.9rem", | ||
fontWeight: "normal", | ||
borderWidth: "1px", | ||
borderStyle: "solid", | ||
borderColor: "text.secondary", | ||
"&:hover": { | ||
opacity: 0.7, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; |
Oops, something went wrong.