reactjshyperlinkmaterial-uitablerowtablecell

Make entire contents of a TableCell into a link conditionally


I've made a table with a folder icon that is clickable link. To improve UX, I'd like to make the clickable area of the link bigger. I would like to make the entire contents of 3 of 4 of the cells in a row should be clickable as a link.

Here's what I mean

enter image description here

I can successfully make each cell contents (as in icons or text) clickable, but that still represents only a small area in each cell.

I've tried wrapping the entire cell contents in the link, but it leads to messy mapping, and still does not make the entire contents of the cell clickable.

Here's where I'm at so far

...

interface IProps extends Omit<unknown, 'children'> {
  folders?: IGetAllRequestDTO<IFolderDTO> | null;
  reload: boolean;
}

const RootFoldersTable = ({ folders, reload }: IProps): JSX.Element => {
  const [selectedRow, setSelectedRow] = useState('');
  const classes = useStyles();
  const dispatch = useDispatch();

  const getCorrectFormat = useCallback(cell => {
    return cell instanceof Date
      ? format(cell as Date, "MM/dd/yyyy hh:mmaaaaa'm'")
      : cell;
  }, []);

  const getAllFolders = () => {
    dispatch(appActions.getAllFoldersRequest({}));
  };

  useEffect(() => {
    getAllFolders();
  }, [reload]);

  const openDialogWithId = (folderId: string) => {
    dispatch(appActions.setDialogFormFolderId(folderId));
    dispatch(appActions.setDialogOpen());
  };

  const onDeleteFolder = (id: string) => {
    dispatch(appActions.deleteFolderRequest(id));
  };

  const tableHeadElements = [
    { label: 'Name', key: 'name', sortable: false },
    { label: 'Last Modified', key: 'updateAt', sortable: false },
    { label: 'Actions', sortable: false }
  ];

    const tableHeadElements = [
        { label: 'Name', key: 'name', sortable: false },
        { label: 'Last Modified', key: 'updateAt', sortable: false },
        { label: 'Actions', sortable: false }
      ];
    
      return (
        <div className={classes.tableContainer}>
          <TableContainer className={classes.tableBodyContainer}>
            <Table className={classes.table} size="small">
              <TableHead>
                <TableRow className={classes.tableHeadRow}>
                  {tableHeadElements.map(e => (
                    <TableCell key={e.key} align="center">
                      {e.label}
                    </TableCell>
                  ))}
                </TableRow>
              </TableHead>
        <TableBody>
          {folders?.items.map((folder: IFolderDTO, index: number) => {
            const { id, name, updatedAt } = folder;
            return (
              <TableRow
                className={classes.tableRow}
                classes={{ selected: classes.selectedRow }}
                selected={selectedRow === id}
                onClick={() => setSelectedRow(id)}
                key={index}
              >
                <Link to={APP_DASHBOARD_CHILD_FOLDER_CONTENTS_PATH(id)}>
                  <TableCell align="center">
        
                    <IconButton color="default" size={'small'}>
                      <FolderIcon fontSize="default" />
                    </IconButton>
        
                  </TableCell>
                </Link>
                {[name, new Date(updatedAt)].map(cell => (
                  <TableCell key={index} align="center">
                    <Link to={APP_DASHBOARD_CHILD_FOLDER_CONTENTS_PATH(id)}>
                      {getCorrectFormat(cell)}
                    </Link>
                  </TableCell>
                ))}
                <FolderActionsMenu
                  folderId={id}
                  onDeleteFolder={onDeleteFolder}
                  openDialogWithId={openDialogWithId}
                />
              </TableRow>
            );
          })}
        </TableBody>

Thanks!


Solution

  • You can create a header cell data array that describes anything you need to render the TableCell:

    const headerCellData = [
      {
        name: 'Calories',
        link: '/',
      },
      {
        name: 'Fat (g)',
        link: '/?filter=fat',
        align: 'right',
      },
      {
        name: 'Carbs (g)',
        link: '/?filter=carbs',
        align: 'right',
      },
      {
        name: 'Protein (g)',
        align: 'right',
      },
    ];
    

    Then map each data item to the TableCell:

    <TableHead>
      <TableRow>
        {headerCellData.map((c) => (
          <TableCell align={c.align}>
            {c.link ? <Link to={c.link}>{c.name}</Link> : c.name}
          </TableCell>
        ))}
      </TableRow>
    </TableHead>
    

    make the entire contents of the cell clickable

    If you want the entire cell clickable (not just the text), you need to play around with CSS a little bit. Remove the padding of TableCell where it's unclickable and set the padding of the container that holds the link to 16px which is the padding of the TableCell we just removed:

    <TableCell align={c.align} sx={{ padding: 0 }}>
      <Box sx={{ padding: '16px' }}>
        {c.link ? <Link to={c.link}>{c.name}</Link> : c.name}
      </Box>
    </TableCell>
    

    Codesandbox Demo