0

I have a sticky Material UI List. I also have another list with just the headers which when clicked on will scroll the sticky list and take me to the corresponding category. However, I am not able to make it work when I scroll through the sticky list and the corresponding list header on the other list should be selected. I am using @makotot/ghostui to achieve scrollspy but its not working. Please advice.

This is my code.

import * as React from "react";
import Grid from "@mui/material/Grid";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import ListSubheader from "@mui/material/ListSubheader";
import ListItemButton from "@mui/material/ListItemButton";
import Divider from "@mui/material/Divider";
import { map } from "lodash";
import { Scrollspy } from "@makotot/ghostui";

const categories = [0, 1, 2, 3, 4];

export default function PinnedSubheaderList() {
  const sectionRefs = React.useMemo(
    () => categories.map((i) => React.createRef<HTMLUListElement>()),
    []
  );

  const [selected, setSelected] = React.useState<number>(0);

  const handleChangeCategory = (category: number): void => {
    let categoryItem = document.getElementById(category.toString());
    categoryItem &&
      categoryItem.scrollIntoView({ behavior: "smooth", block: "start" });
    setSelected(category);
  };

  return (
    <Grid container spacing={2}>
      <Scrollspy sectionRefs={sectionRefs} offset={-100}>
        {({ currentElementIndexInViewport }) => (
          <>
            {console.log(
              "currentElementIndexInViewport",
              currentElementIndexInViewport
            )}
            <Grid item md={6} xs={4}>
              <List
                sx={{ border: `1px solid grey`, my: 2 }}
                dense
                disablePadding
                data-cy="nav-wrapper"
              >
                {map(categories, (category, index) => (
                  <React.Fragment key={category}>
                    <ListItem disablePadding data-cy={`nav-item`}>
                      <ListItemButton
                        selected={currentElementIndexInViewport === index}
                        onClick={() => handleChangeCategory(category)}
                      >
                        <ListItemText primary={category} />
                      </ListItemButton>
                    </ListItem>
                    <Divider />
                  </React.Fragment>
                ))}
              </List>
            </Grid>
            <Grid item md={6} xs={4}>
              <List
                sx={{
                  width: "100%",
                  bgcolor: "background.paper",
                  position: "relative",
                  overflow: "auto",
                  maxHeight: 300,
                  "& ul": { padding: 0 }
                }}
                subheader={<li />}
                data-cy="section-wrapper"
              >
                {categories.map((sectionId, sectionIndex) => (
                  <li key={`section-${sectionId}`} id={sectionId.toString()}>
                    <ul ref={sectionRefs[sectionIndex]}>
                      <ListSubheader>{`I'm sticky ${sectionId}`}</ListSubheader>
                      {[0, 1, 2].map((item) => (
                        <ListItem key={`item-${sectionId}-${item}`}>
                          <ListItemText primary={`Item ${item}`} />
                        </ListItem>
                      ))}
                    </ul>
                  </li>
                ))}
              </List>
            </Grid>
          </>
        )}
      </Scrollspy>
    </Grid>
  );
}

Stackblitz link: Link

1 Answer 1

1

You need to pass rootSelector prop to your Scrollspy component.

In order to do this, first, you can set a unique id to your List component like this:

<List
    sx={{
      width: "100%",
      bgcolor: "background.paper",
      position: "relative",
      overflow: "auto",
      maxHeight: 300,
      "& ul": { padding: 0 }
    }}
    subheader={<li />}
    data-cy="section-wrapper"
    id="my-list-root"
>

And then pass its querySelector string ("#my-list-root") to the Scrollspy like this:

<Scrollspy
    sectionRefs={sectionRefs}
    offset={-100}
    rootSelector="#my-list-root"
>

You can take a look at this sandbox for a live working example of this solution.

Sign up to request clarification or add additional context in comments.

5 Comments

In this specific case, why does the selection default to the last value? codesandbox.io/s/scrollspy-root-selector-forked-h57igt?file=/…
Currently, your sandbox throws error includes is not defined.
Because the header and the content of the last value are visible. This component cannot work properly on such small data. Either you need to decrease the max height of the list, or increase the padding of the list items.
Thanks. But the left panel should always target the top element of the list, right?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.