当我将鼠标悬停在 Datagrid 中的任何行上时,我希望“分析”按钮从概述的变体变为包含的变体。当鼠标悬停在该行上时,我无法触发任何事件,也找不到有关当鼠标位于该行内时如何更新/重新呈现该行内的单元格的任何信息。
"@mui/x-data-grid": "^5.17.25", "@mui/x-data-grid-generator": "^6.0.0", "@mui/x-data-grid-pro": "^6.0.0",
import React, { useRef, useState, useEffect } from "react";
import { DataGrid, GridRowsProp, GridColDef } from "@mui/x-data-grid";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import { useTheme } from "@mui/system";
import Link from "next/link";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import SearchIcon from "@mui/icons-material/Search";
import AddIcon from "@mui/icons-material/Add";
import CircularProgress from "@mui/material/CircularProgress";
import { alpha, styled, lighten } from "@mui/material/styles";
export default function PropertiesList({ newProperties }) {
const theme = useTheme();
const boxRef = useRef(null);
const [searchText, setSearchText] = useState("");
const columns = getColumns(theme);
function getColumns(theme) {
// commented because irrelevant
return [
{
field: "id",
headerName: "Actions",
width: 150,
renderCell: (params) => {
return (
<Box
sx={{
display: "flex",
justifyContent: "space-between",
width: "100%"
}}
>
<Link
href="/properties/[id]"
as={`/properties/${params.row.original_doc || params.row.id}`}
>
<Button
size="small"
variant="outlined"
startIcon={<CalculateIcon />}
sx={{
backgroundColor:
hoveredRowId === params.id
? theme.palette.success.main
: ""
}}
>
Analyze
</Button>
</Link>
</Box>
);
}
}
];
}
useEffect(() => {
if (!boxRef.current) return;
const screenHeight = window.innerHeight;
boxRef.current.style.height = `${screenHeight - 120}px`;
}, []);
const handleRowOver = (params) => {
// change the analyze button from "outlined" to "contained" when hovered.
// The below console.log does not trigger.
console.log(`Row ${params.id} is being hovered over`);
};
return (
<Box ref={boxRef}>
{!newProperties && (
<Box
sx={{
height: "calc(100vh - 160px)",
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<CircularProgress size={32} />
</Box>
)}
{newProperties && (
<>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
background: theme.palette.background.background2,
marginTop: 3,
// marginBottom: 1,
padding: 2,
border: "1px solid " + theme.palette.contrast.contrast1,
borderTopLeftRadius: 8,
borderTopRightRadius: 8
}}
>
<TextField
label="Search for property"
placeholder=""
sx={{ marginTop: 1, marginBottom: 1 }}
onChange={(event) => setSearchText(event.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
)
}}
/>
<Link href="/properties/add">
<Button
size="medium"
variant="contained"
sx={{ height: 50 }}
startIcon={<AddIcon />}
>
Add Property
</Button>
</Link>
</Box>
<DataGrid
rowMouseEnter={handleRowOver}
sx={{
border: "1px solid " + theme.palette.contrast.contrast1,
height: "calc(100vh - 280px)",
background: theme.palette.background.background1,
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar": {
height: "0.4em",
width: "0.4em"
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-track": {
background: theme.palette.contrast.contrast1
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb": {
backgroundColor: theme.palette.contrast.contrast2
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb:hover": {
background: theme.palette.contrast.default
},
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8
}}
rows={newProperties.filter(
(row) =>
(row.address &&
row.address
.toLowerCase()
.includes(searchText.toLowerCase())) ||
(row.city &&
row.city.toLowerCase().includes(searchText.toLowerCase())) ||
(row.state &&
row.state.toLowerCase().includes(searchText.toLowerCase())) ||
(row.zip &&
row.zip
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()))
)}
columns={columns}
pageSize={13}
disableColumnFilter
disableSelectionOnClick
disableColumnSelector
/>
</>
)}
</Box>
);
}
正如@VonC 在他的回答中提到的,您可以使用 slotProps 将道具传递给行元素,特别是 onMouseEnter 和 onMouseLeave。使用 here 中描述的技术,我能够以相当简洁的方式重现您试图实现的行为。
主要思想是在 onMouseEnter 和 onMouseLeave 中触发一个事件,我们将在自定义按钮组件中订阅该事件。
为了实现不同行之间的事件隔离,我们会在事件名称中包含行id。
没有上下文很难运行你的组件,所以为了演示原理,我自己构建了一个最小的DataGrid。
你可以在这里看到一个活生生的例子:
代码:
import React, { FC, useState, useEffect } from "react";
import Button from "@mui/material/Button";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import CalculateIcon from "@mui/icons-material/Calculate";
const CustomButtonElement: FC<{ rowId: number | string }> = ({ rowId }) => {
const [rowHovered, setRowHovered] = useState(false);
useEffect(() => {
const handleCustomEvent = (e) => setRowHovered(e.detail.hovered);
document.addEventListener(`row${rowId}HoverChange`, handleCustomEvent);
// cleanup listener
return () =>
document.removeEventListener(`row${rowId}HoverChange`, handleCustomEvent);
}, [rowId]);
return (
<Button variant={rowHovered ? "outlined" : "contained"}>
<CalculateIcon />
</Button>
);
};
export default function DataGridDemo() {
const rows = [
{ id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
{ id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
{ id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
{ id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
{ id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null }
];
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 90 },
{
field: "",
headerName: "Action",
renderCell: (params) => <CustomButtonElement rowId={params.id} />
},
{ field: "firstName", headerName: "First Name", width: 90 },
{ field: "lastName", headerName: "Last Name", width: 90 }
];
const handleRowHovered = (event: React.MouseEvent<HTMLElement>) => {
const rowId = event.currentTarget?.dataset?.id;
document.dispatchEvent(
new CustomEvent(`row${rowId}HoverChange`, { detail: { hovered: true } })
);
};
const handleRowLeaved = (event: React.MouseEvent<HTMLElement>) => {
const rowId = event.currentTarget?.dataset?.id;
document.dispatchEvent(
new CustomEvent(`row${rowId}HoverChange`, { detail: { hovered: false } })
);
};
return (
<DataGrid
rows={rows}
columns={columns}
slotProps={{
row: {
onMouseEnter: handleRowHovered,
onMouseLeave: handleRowLeaved
}
}}
/>
);
}
为了解决组件离开视图时状态丢失的问题(由于 unmount ),我添加了一个 useEffect 将在每个按钮上运行 mount 并检查鼠标是否悬停在按钮行元素上。为此,我使用 matches 和 apiRef 对象通过其上下文对 DataGrid 行元素进行更多本机访问。
事实证明,由于相同的 apiRef 和
useGridApiEventHandler
钩子,您可以以更原生的方式订阅 events(无需创建自定义事件),因此代码更加简洁和富有表现力。
Updated Code(上面的Codesandbox也更新了):
import React, { FC, useState, useEffect } from "react";
import Button from "@mui/material/Button";
import {
DataGrid,
GridColDef,
GridEventListener,
useGridApiContext,
useGridApiEventHandler
} from "@mui/x-data-grid";
import CalculateIcon from "@mui/icons-material/Calculate";
const CustomButtonElement: FC<{ rowId: number | string }> = ({ rowId }) => {
const [rowHovered, setRowHovered] = useState(false);
const apiRef = useGridApiContext();
// runs only "onComponentMount"
useEffect(() => {
if (apiRef.current.getRowElement(rowId).matches(":hover"))
setRowHovered(true);
}, []);
const handleRowEnter: GridEventListener<"rowMouseEnter"> = ({ id }) =>
id === rowId && setRowHovered(true);
const handleRowLeave: GridEventListener<"rowMouseLeave"> = ({ id }) =>
id === rowId && setRowHovered(false);
useGridApiEventHandler(apiRef, "rowMouseEnter", handleRowEnter);
useGridApiEventHandler(apiRef, "rowMouseLeave", handleRowLeave);
return (
<Button variant={rowHovered ? "outlined" : "contained"}>
<CalculateIcon />
</Button>
);
};
export default function DataGridDemo() {
const rows = [
{ id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
{ id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
{ id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
{ id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
{ id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null }
];
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 90 },
{
field: "",
headerName: "Action",
renderCell: (params) => <CustomButtonElement rowId={params.id} />
},
{ field: "firstName", headerName: "First Name", width: 90 },
{ field: "lastName", headerName: "Last Name", width: 90 },
{ field: "age", headerName: "Age", width: 90 }
];
return <DataGrid rows={rows} columns={columns} />;
}
MUI DataGrid 不包含任何关于悬停的事件函数。
(你只有像
onCellClick()
,onCellEditStart()
,onCellKeyDown()
,......这样的功能)
我考虑过像
onMouseEnter
和onMouseLeave
这样的事件监听器,但是,正如在这里提到的那样,它会导致如此多的渲染。
也许使用 sx prop 将有助于使用
:hover
: 设计这些块的样式
类似的东西:
function getColumns(theme) {
return [
{
field: "id",
headerName: "Actions",
width: 150,
renderCell: (params) => {
return (
<Box
sx={{
display: "flex",
justifyContent: "space-between",
width: "100%",
'&:hover button': {
backgroundColor: theme.palette.success.main,
border: '1px solid transparent',
color: theme.palette.common.white,
},
}}
>
<Link
href="/properties/[id]"
as={`/properties/${params.row.original_doc || params.row.id}`}
>
<Button
size="small"
variant="outlined"
startIcon={<CalculateIcon />}
sx={{
borderColor: theme.palette.success.main,
'&:hover': {
backgroundColor: theme.palette.success.main,
borderColor: 'transparent',
color: theme.palette.common.white,
},
}}
>
Analyze
</Button>
</Link>
</Box>
);
},
},
];
}
关联
<DataGrid
onRowMouseEnter={handleRowOver}
// Other props
/>
(不是
rowMouseEnter
)
使用与 DataGrid 行中显示的类似的想法:
<DataGrid
// Other props
slotProps={{
row: {
onMouseEnter: (event) => handleRowOver(Number(event.currentTarget.getAttribute('data-id'))),
onMouseLeave: () => console.log('Mouse left the row'),
},
}}
/>
与:
const handleRowOver = (rowId) => {
console.log(`Row ${rowId} is being hovered over`);
};
当鼠标悬停在DataGrid中的一行上时,会调用
handleRowOver
函数,可以在浏览器控制台看到日志。:hover
属性中的 sx
伪类处理。