I looking for help sorting out problem I encountered recently while working with React-Window and MUI Collapsible Table Rows.
In general react-window render row for each of my item in the array, with some of the content hidden till we press arrow. When pressed, below our column appear new one with hidden content.
function Row({ index, style }) {
const [showList, toggleShowList] = useState(false);
return (
<Fragment>
<TableRow sx={style}>
<TableCell>Visable Row:{index}</TableCell>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => toggleShowList(!showList)}
>
{showList ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
</TableRow>
<TableRow sx={{ style, display: showList ? "table-row" : "none" }}>
<TableCell>hidden row:{index}</TableCell>
</TableRow>
</Fragment>
);
}
Above example is based on MUI Table component page. Problem arise when I passing it to React-Window as its display hidden content out of place.
I try to using useRef() for calculating Row Height but its has no effect on hidden element.
I open for suggestions and solutions. I created an example on code Sandbox with both
how its actually work and how (it should) I want it to work.
Related
I have a Sveltekit app and for my home route I want to display a table based on carbon components. I want to filter the data displayed in the table by adding a left panel right next to the table. I'm basically looking for their example
but I don't know how they solved it. I know the navbar has a navigation panel but this panel has nothing to do with the navigation and should only appear for my home route.
I tried to modify the official codebox sample to show what I have so far ( please have mercy, I've never used React before ). I hope the technology ( React / Vue / Svelte ) shouldn't matter.
import React from "react";
import { render } from "react-dom";
import {
Header,
HeaderName,
HeaderNavigation,
HeaderMenuItem,
Theme,
Content,
DataTable,
TableContainer,
Table,
TableHead,
TableRow,
TableHeader,
TableBody,
TableCell
} from "#carbon/react";
const App = () => (
<Theme theme="g100">
<Header>
<HeaderName>Nav goes here</HeaderName>
<HeaderNavigation>
<HeaderMenuItem>Link 1</HeaderMenuItem>
</HeaderNavigation>
</Header>
<Content>
{/* TODO add sidebar for filters right next to the table */}
<DataTable
rows={[
{
id: 1,
name: "First element"
}
]}
headers={[
{
key: "name",
header: "Name"
}
]}
>
{({ rows, headers, getHeaderProps, getTableProps }) => (
<TableContainer title="DataTable">
<Table {...getTableProps()}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</DataTable>
</Content>
</Theme>
);
render(<App />, document.getElementById("root"));
I thought about using a Grid component but then both columns get the same width and the grid comes with a big horizontal margin.
If the grid is the right component for the job, how can I tell the first column ( filters ) to use the width it needs and the second column ( table ) fills the rest?
Do you have any ideas how to setup a left panel?
The Grid is the component intended for layout like this. It has various options to modify its behavior and should be configured to be responsive.
It has a max-width that is probably intended to aid with readability as long lines on larger screens are harder to process. This limit can be removed by setting fullWidth.
There also is some padding that can be removed via noGutter, noGutterLeft and noGutterRight. These exist for Grid, Row and Column.
The responsiveness of the grid system is documented in more detail in the design system specification. The number of "virtual" columns is dependent on the screen size. This count ranges from 4 up to 16.
E.g. in this case you might want to set sm={4} so the column jumps to full-width above the table on small screens and set md={2} to take up less space otherwise.
<Content>
<Grid fullWidth noGutter>
<Row>
<Column sm={4} md={2}>
<div class="panel">
Left Panel
</div>
</Column>
<Column>Content</Column>
</Row>
</Grid>
</Content>
REPL
I have an MUI card which is dynamically mapped depending on the JSON response from an API. Each card has a comment. Clicking on the comment opens another hidden card which displays the complete comment. Because of how I am using state, each time I click on a comment, the hidden card will open for all mapped cards, instead of just the one I clicked. How can I change this to just open the one hidden card?
const [showContextCard, setShowContextCard] = useState(false);
...
<Box className={classes.groupCards}>
{questionGroups?.displaygroups?.ConslutingQuestions &&
questionGroups?.displaygroups?.ConslutingQuestions?.map((group, groupIndex) => {
function handleShowContext() {
if (!showContextCard) {
setShowContextCard(true);
} else setShowContextCard(false);
}
return (
<>
<Card className={classes.card}>
<CardHeader className={classes.cardTitle} title={group.GroupName} subheader={group.OwnerName} />
</Card>
<Card hidden={!showContextCard} className={classes.contextCard}>Comment Card</Card>
</>
);
})}
</Box>
I m building a React 18.2 app using MUI 5.10.5, but I have run into a problem creating a <Menu /> element that opens in response to a button click. The menu appears, but it seems the anchorEl is not configured properly, because the menu appears in the top-left of the screen and the browser console has this complaint:
The issue is complicated by the fact that the menus pertain to each row in a <Table /> so, besides anything else, I am not sure if I am supposed to have a single menu outside the table, or repeat the menu for each row in the table. Which seems expensive. But I have organised the code so that the menu is currently duplicated.
export const MetricsQuery = () => {
const [menuState, setMenuState] = useState<{
open: boolean;
anchorEl: null | Element;
}>({open: false, anchorEl: null});
const handleOpenMenuClick = ({currentTarget}: MouseEvent) =>
setMenuState({open: true, anchorEl: currentTarget});
return (
<TableContainer component={Paper}>
<StyledTable>
{/*Header*/}
<TableBody>
{queryMetrics.map((row: QueryMetric) => (
<TableRow>
<TableCell component="th" scope="row">
{row.locationName}
</TableCell>
<TableCell>{row.deviceName}</TableCell>
<TableCell>{row.pointName}</TableCell>
<TableCell>
<Stack>
<IconButton
id="basic-button"
aria-controls={menuState.open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={menuState.open ? 'true' : undefined}
onClick={handleOpenMenuClick}
>
<MoreVertIcon sx={{color: theme.palette.primary.light}} fontSize="small"/>
</IconButton>
<Menu
anchorEl={menuState.anchorEl}
open={menuState.open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button'
}}
>
<MenuItem onClick={handleClose}>Remove</MenuItem>
<MenuItem onClick={handleClose}>Disable</MenuItem>
<MenuItem onClick={handleClose}>Pin to Top</MenuItem>
</Menu>
</Stack>
</TableCell>
</TableRow>
))}
</TableBody>
</StyledTable>
</TableContainer>
)
}
I have tried quite a number of configurations; such as
omitting the ID of the <IconButton />
making this ID unique for each row
having a single menu outside of the <StyledTable />
following the menu documentation more literally
Not sure what else to try..suggestions?
I am not sure if I am supposed to have a single menu outside the table, or repeat the menu for each row in the table.
You can have just one Menu outside of the table. The anchorEl will determine where it is positioned.
You can also have a Menu for every table row, as it will not be rendered to the DOM when it is closed. The advantage of this approach is that each menu can transition independently, so that one can animate closed while another animates open. But if you want to do it this way then you will need a separate menuState for each row, as they all have their own anchors.
What you have right now will open many copies of the same menu, all on top of each other, when any one of the buttons is clicked.
I would move the button and its menu into a new component so that you can have a separate state for each.
const RowMenu = ({ id }: QueryMetric) => {
const [menuState, setMenuState] = useState<{
open: boolean;
anchorEl: null | Element;
}>({ open: false, anchorEl: null });
const handleOpenMenuClick = ({ currentTarget }: { currentTarget: Element }) =>
setMenuState({ open: true, anchorEl: currentTarget });
const handleClose = () => {
setMenuState((prevState) => ({ ...prevState, open: false }));
};
const menuId = `basic-menu-${id}`;
const buttonId = `basic-button-${id}`;
return (
<>
<IconButton
id={buttonId}
aria-controls={menuState.open ? menuId : undefined}
aria-haspopup="true"
aria-expanded={menuState.open ? "true" : undefined}
onClick={handleOpenMenuClick}
sx={{ alignSelf: "center" }}
>
<MoreVertIcon
sx={{ color: (theme) => theme.palette.primary.light }}
fontSize="small"
/>
</IconButton>
<Menu
id={menuId}
anchorEl={menuState.anchorEl}
open={menuState.open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": buttonId
}}
>
<MenuItem onClick={handleClose}>Remove</MenuItem>
<MenuItem onClick={handleClose}>Disable</MenuItem>
<MenuItem onClick={handleClose}>Pin to Top</MenuItem>
</Menu>
</>
);
};
export const MetricsQuery = () => {
return (
<TableContainer component={Paper}>
<StyledTable>
{/*Header*/}
<TableBody>
{queryMetrics.map((row) => (
<TableRow key={row.id}>
<TableCell component="th" scope="row">
{row.locationName}
</TableCell>
<TableCell>{row.deviceName}</TableCell>
<TableCell>{row.pointName}</TableCell>
<TableCell>
<Stack>
<RowMenu {...row} />
</Stack>
</TableCell>
</TableRow>
))}
</TableBody>
</StyledTable>
</TableContainer>
);
};
CodeSandbox Link
making this ID unique for each row
If you have ids in your DOM then they need to be unique. Your aria attributes don't make sense if they are all the same. But this is a separate issue and I don't think that this would cause any of the issues that you are having with anchor elements.
-- Material-UI / React / Redux --
I have a material-ui table. Inside each <TableRow> there are some <TableCell> components with their own <Chip> components. These <Chip> components are rendering text through the label property.
I need to be able to extract the label inside the onClick handler, which in my case is the chipFilter() function.
I am going to use that label to filter my redux state and return new data for the larger component rendering the table.
Click Handler
chipFilter = () => {
console.log(this)
console.log(this.props)
console.log(this.props.label)
}
Component render
Each row that is rendered in the table:
<TableRow key={n.id}>
<TableCell
component="th"
align="center"
scope="row">
<Chip
label={n.procedure}
variant="outlined"
color="primary"
clickable={true}
onClick={this.chipFilter} />
</TableCell>
<TableCell align="center">
<Chip
label={n.doctor}
variant="outlined"
color="primary"
clickable={true}
onClick={this.chipFilter} />
</TableCell>
.
.
.
</TableRow>
Thanks for the help!!
A simple solution would be to update your onClick handler so that the n object which contains the meta data of the clicked <Chip> is passed through to chipFilter() like so:
<Chip label={n.procedure} variant="outlined" color="primary" clickable={true}
onClick={ () => this.chipFilter(n) } />
And then update the chipFilter function as follows:
/* Item argument contains data for clicked chip component */
chipFilter = (item) => {
console.log(this)
console.log(item)
console.log(item.label)
}
I have made a table using Material UI where I have two buttons in the first column of every row. I wish to edit/delete rows on clicking these but Im stuck on logic. Is it even possible with my implementation ? If not then what's the preferred way of doing so?
render() {
var deleteIcon =
(<IconButton onClick={console.log("delete")}>
<DeleteIcon color="secondary" />
</IconButton>
);
const editIcon = (
<IconButton onClick={console.log("edited")}>
<EditIcon color="primary" />
</IconButton>
);
return(
<TableBody>
{this.state.serviceData.map(n => {
return (
<TableRow key={n.id}>
<TableCell style={styles.editor} component="th" scope="row">
{deleteIcon}
{editIcon}
</TableCell>
<TableCell>{n.domain}</TableCell>
<TableCell>{n.service_name}</TableCell>
</TableCell>
</TableRow>
)};
And my result is :
Building on #st4rl00rd's comment, I was able to tie the buttons using :
const editIcon = (
<IconButton onClick={this.editItem}>
<EditIcon color="primary" />
</IconButton>
);
and binding them in the constructor. I was able to get the selected row data by doing :
<TableBody>
{this.state.serviceData.map(n => {
return (
<TableRow key={n.id} onClick={this.getData.bind(this,n)}>
I have recreated your problem and solved the problem with my logic.
I passed the index of each element as a parameter to the handler functions.
Eg:
const editIcon = index => (
<IconButton onClick={() => this.editComponent(index)}>
<EditIcon color="primary" />
</IconButton>
);
DELETION
For deletion, pass the index as params to the handler function and delete the element at specified index using splice operator.
deleteComponent(index) {
const serviceData = this.state.serviceData.slice();
serviceData.splice(index, 1);
this.setState({ serviceData });
}
EDITING
I have used a state called index to keep track of the index the user is currently editing. Initially the index is -1
So whenever the user clicks edit button the editedIndex is updated.
editComponent(index) {
this.setState({ editedIndex: index });
}
I created two TextField Component which is shown at the specified cell (the cell where editbutton is clicked)
const editDomain = (
<TextField
id="domain"
label="Domain"
className={classes.textField}
value={this.state.editedDomain}
margin="normal"
onChange={this.handleChange('editedDomain')}
/>
);
So Whenever the rendering component Index is equal to editedIndex the editing Compomemts are shown at corresponding Tablecell
<TableCell>
{serviceData.indexOf(n) === editedIndex
? editDomain
: n.domain}
</TableCell>
I suppose you want to do this
I have done same using React-Table here is the link for my project repo you can consider this as an example:
https://github.com/AdnanShah/ReactJS-KretaHub-/blob/Thank_You_Init/src/app/routes/dashboard/routes/Default/rows.js