Search code examples
javascriptreactjsmaterial-ui

Have two secondary action elements in a list


I have created a list in react which has the following structure:

  • Avatar
  • Text
  • Edit icon
  • Delete icon

I have created the structure fine until the delete icon. How can I add this? Currently, it is overlapping the edit icon as both are ListItemSecondaryAction but I can't find on the documentation how to add an additional object and what it should be called? https://material-ui.com/components/lists/

Current implementation:

<List>
    <ListItemAvatar>
        <Avatar src="image" />
    </ListItemAvatar>
    <ListItemText primary="name" />
    <ListItemSecondaryAction>
        <IconButton>
            <EditIcon />
        </IconButton>
    </ListItemSecondaryAction>
    <ListItemSecondaryAction>
        <IconButton>
            <DeleteIcon />
        </IconButton>
    </ListItemSecondaryAction>
</List>

enter image description here


Solution

  • It is almost sufficient to just put both actions in one ListItemSecondaryAction (as indicated by comments and another answer). The only issue is that if you have long content it will overlap the first icon.

    Here are the styles for the secondary action from ListItem:

      /* Styles applied to the `component` element if `children` includes `ListItemSecondaryAction`. */
      secondaryAction: {
        // Add some space to avoid collision as `ListItemSecondaryAction`
        // is absolutely positioned.
        paddingRight: 48,
      },
    

    The paddingRight: 48 will not be sufficient for two icons. You can customize this as follows:

    const ListItemWithWiderSecondaryAction = withStyles({
      secondaryAction: {
        paddingRight: 96
      }
    })(ListItem);
    

    Here is a full working v4 example (v5 example further down) that shows the first two list items without this customization (so overlap occurs) and the second two with the fix:

    import React from "react";
    import { makeStyles, withStyles } from "@material-ui/core/styles";
    import List from "@material-ui/core/List";
    import ListItem from "@material-ui/core/ListItem";
    import ListItemIcon from "@material-ui/core/ListItemIcon";
    import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
    import ListItemText from "@material-ui/core/ListItemText";
    import Checkbox from "@material-ui/core/Checkbox";
    import IconButton from "@material-ui/core/IconButton";
    import CommentIcon from "@material-ui/icons/Comment";
    import DeleteIcon from "@material-ui/icons/Delete";
    
    const useStyles = makeStyles(theme => ({
      root: {
        width: "100%",
        maxWidth: 360,
        backgroundColor: theme.palette.background.paper
      }
    }));
    
    const ListItemWithWiderSecondaryAction = withStyles({
      secondaryAction: {
        paddingRight: 96
      }
    })(ListItem);
    
    export default function CheckboxList() {
      const classes = useStyles();
      const [checked, setChecked] = React.useState([0]);
    
      const handleToggle = value => () => {
        const currentIndex = checked.indexOf(value);
        const newChecked = [...checked];
    
        if (currentIndex === -1) {
          newChecked.push(value);
        } else {
          newChecked.splice(currentIndex, 1);
        }
    
        setChecked(newChecked);
      };
    
      return (
        <>
          <List className={classes.root}>
            {[0, 1].map(value => {
              const labelId = `checkbox-list-label-${value}`;
    
              return (
                <ListItem
                  key={value}
                  role={undefined}
                  dense
                  button
                  onClick={handleToggle(value)}
                >
                  <ListItemIcon>
                    <Checkbox
                      edge="start"
                      checked={checked.indexOf(value) !== -1}
                      tabIndex={-1}
                      disableRipple
                      inputProps={{ "aria-labelledby": labelId }}
                    />
                  </ListItemIcon>
                  <ListItemText
                    id={labelId}
                    primary={`Line item ${value +
                      1} with some more text to make it longer`}
                  />
                  <ListItemSecondaryAction>
                    <IconButton aria-label="comments">
                      <CommentIcon />
                    </IconButton>
                    <IconButton edge="end" aria-label="delete">
                      <DeleteIcon />
                    </IconButton>
                  </ListItemSecondaryAction>
                </ListItem>
              );
            })}
          </List>
          <List className={classes.root}>
            {[2, 3].map(value => {
              const labelId = `checkbox-list-label-${value}`;
    
              return (
                <ListItemWithWiderSecondaryAction
                  key={value}
                  role={undefined}
                  dense
                  button
                  onClick={handleToggle(value)}
                >
                  <ListItemIcon>
                    <Checkbox
                      edge="start"
                      checked={checked.indexOf(value) !== -1}
                      tabIndex={-1}
                      disableRipple
                      inputProps={{ "aria-labelledby": labelId }}
                    />
                  </ListItemIcon>
                  <ListItemText
                    id={labelId}
                    primary={`Line item ${value +
                      1} with some more text to make it longer`}
                  />
                  <ListItemSecondaryAction>
                    <IconButton aria-label="comments">
                      <CommentIcon />
                    </IconButton>
                    <IconButton edge="end" aria-label="delete">
                      <DeleteIcon />
                    </IconButton>
                  </ListItemSecondaryAction>
                </ListItemWithWiderSecondaryAction>
              );
            })}
          </List>
        </>
      );
    }
    

    Edit Wider secondary action

    Below is an equivalent v5 example. The changes compared to the v4 version are:

    • Uses styled instead of withStyles and makeStyles
    • Uses ListItemButton instead of the deprecated ListItem button prop
    • Add disablePadding prop to the ListItem elements. This is needed when leveraging ListItemButton in order to keep the styling equivalent to the v4 example
    • Package names updated from @material-ui/core and @material-ui/icons to @mui/material and @mui/icons-material
    import * as React from "react";
    import { styled } from "@mui/material/styles";
    import List from "@mui/material/List";
    import ListItem from "@mui/material/ListItem";
    import ListItemIcon from "@mui/material/ListItemIcon";
    import ListItemText from "@mui/material/ListItemText";
    import ListItemButton from "@mui/material/ListItemButton";
    import ListItemSecondaryAction from "@mui/material/ListItemSecondaryAction";
    import Checkbox from "@mui/material/Checkbox";
    import IconButton from "@mui/material/IconButton";
    import CommentIcon from "@mui/icons-material/Comment";
    import DeleteIcon from "@mui/icons-material/Delete";
    
    const StyledList = styled(List)(({ theme }) => ({
      width: "100%",
      maxWidth: 360,
      backgroundColor: theme.palette.background.paper
    }));
    
    const ListItemWithWiderSecondaryAction = styled(ListItem)(({ theme }) => ({
      "&.MuiListItem-secondaryAction": {
        paddingRight: 96
      }
    }));
    
    export default function CheckboxList() {
      const [checked, setChecked] = React.useState([0]);
    
      const handleToggle = (value) => () => {
        const currentIndex = checked.indexOf(value);
        const newChecked = [...checked];
    
        if (currentIndex === -1) {
          newChecked.push(value);
        } else {
          newChecked.splice(currentIndex, 1);
        }
    
        setChecked(newChecked);
      };
    
      return (
        <>
          <StyledList>
            {[0, 1].map((value) => {
              const labelId = `checkbox-list-label-${value}`;
    
              return (
                <ListItem key={value} disablePadding>
                  <ListItemButton
                    role={undefined}
                    dense
                    onClick={handleToggle(value)}
                  >
                    <ListItemIcon>
                      <Checkbox
                        edge="start"
                        checked={checked.indexOf(value) !== -1}
                        tabIndex={-1}
                        disableRipple
                        inputProps={{ "aria-labelledby": labelId }}
                      />
                    </ListItemIcon>
                    <ListItemText
                      id={labelId}
                      primary={`Line item ${
                        value + 1
                      } with some more text to make it longer`}
                    />
                  </ListItemButton>
                  <ListItemSecondaryAction>
                    <IconButton aria-label="comments">
                      <CommentIcon />
                    </IconButton>
                    <IconButton edge="end" aria-label="delete">
                      <DeleteIcon />
                    </IconButton>
                  </ListItemSecondaryAction>
                </ListItem>
              );
            })}
          </StyledList>
          <StyledList>
            {[2, 3].map((value) => {
              const labelId = `checkbox-list-label-${value}`;
    
              return (
                <ListItemWithWiderSecondaryAction key={value} disablePadding>
                  <ListItemButton
                    role={undefined}
                    dense
                    onClick={handleToggle(value)}
                  >
                    <ListItemIcon>
                      <Checkbox
                        edge="start"
                        checked={checked.indexOf(value) !== -1}
                        tabIndex={-1}
                        disableRipple
                        inputProps={{ "aria-labelledby": labelId }}
                      />
                    </ListItemIcon>
                    <ListItemText
                      id={labelId}
                      primary={`Line item ${
                        value + 1
                      } with some more text to make it longer`}
                    />
                  </ListItemButton>
                  <ListItemSecondaryAction>
                    <IconButton aria-label="comments">
                      <CommentIcon />
                    </IconButton>
                    <IconButton edge="end" aria-label="delete">
                      <DeleteIcon />
                    </IconButton>
                  </ListItemSecondaryAction>
                </ListItemWithWiderSecondaryAction>
              );
            })}
          </StyledList>
        </>
      );
    }
    

    Edit wider secondary action