Diogo
Diogo

Reputation: 77

How to show the horizontal scrollbar without the need to scroll down?

I am using react-split and I would like the first pane to show the horizontal scrollbar without needing to scroll down.

As of right now, to scroll horizontally (using the scrollbar) you have the scroll down first.

I`d like to have both scrollbars showing.

My code: https://codesandbox.io/s/vibrant-flower-ueywiu

App

function App() {
  return (
    <Split
      className="split"
      direction="vertical"
      minSize={0}
      snapOffset={10}
      dragInterval={5}
      sizes={[30, 70]}
    >
      <Market />

      <chakra.div bg="blue">
        <Center h="100%" p="16" textAlign="center">
          <Heading color="white">
            How to show the horizontal scrollbar without the need to scroll
            down?
          </Heading>
        </Center>
      </chakra.div>
    </Split>
  );
}

Market

const Market = (props) => {
  return (
    <chakra.div {...props} overflow="auto">
      <Tabs isLazy>
        <TabList>
          <Tab>Acções</Tab>
          <Tab>PSI 20</Tab>
        </TabList>

        <TabPanels>
          <TabPanel padding={0}>
            <MarketTable />
          </TabPanel>

          <TabPanel padding={0}>
            <p>Empty</p>
          </TabPanel>
        </TabPanels>
      </Tabs>
    </chakra.div>
  );
};

MarketTable

const MarketTable = () => {
  return (
    <TableContainer>
      <Table size="sm">
        <Thead>
          <Tr>
            <Th>Título</Th>
            <Th>Cotacao</Th>
            <Th>Data/Hora</Th>
            <Th>Praça</Th>
            <Th>Variação</Th>
            <Th>CMP</Th>
            <Th>VND</Th>
            <Th>Min</Th>
            <Th>Max</Th>
            <Th>Volume</Th>
          </Tr>
        </Thead>

        <Tbody>
          {data.map((each) => (
            <Tr key={each.nome}>
              <Td title={String(each.nome ?? EMPTY_CHAR)}>
                {each.nome ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.cotacao ?? EMPTY_CHAR)}>
                {each.cotacao ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.dataHora ?? EMPTY_CHAR)}>
                {each.dataHora ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.praca.nome ?? EMPTY_CHAR)}>
                {each.praca.nome ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.variacaoPercent ?? EMPTY_CHAR)}>
                {each.variacaoPercent ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.cmp ?? EMPTY_CHAR)}>
                {each.cmp ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.vnd ?? EMPTY_CHAR)}>
                {each.vnd ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.min ?? EMPTY_CHAR)}>
                {each.min ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.max ?? EMPTY_CHAR)}>
                {each.max ?? EMPTY_CHAR}
              </Td>
              <Td title={String(each.volume ?? EMPTY_CHAR)}>
                {each.volume ?? EMPTY_CHAR}
              </Td>
            </Tr>
          ))}
        </Tbody>
      </Table>
    </TableContainer>
  );
};

Upvotes: 2

Views: 642

Answers (1)

AttemptedMastery
AttemptedMastery

Reputation: 768

First off, I have never used any of these libraries, nor have I ever used Typescript. With that being said, my solution works in spite of these and is fairly easy to follow. The macro-answer is we need to keep track of the highest container size and the table size, find their respective percentages to each other and pass that to <Split />.

We need to add two refs in order to properly set the sizes prop on <Split />. One ref comes from the <TableContainer /> in MarketTable.tsx and the other is put on a <div> wrapper that I added in index. The reason for this wrapper is because it's the highest level container, which means it will always give us the current screen size.

I am not sure if you are familiar with refs but it's a quick way to access a particular DOM element and assess/manipulate it's properties. In this case, we are trying to get the current height of the <TableContainer /> (our table) and the div wrapper.

It looks like this in <TableContainer />:

<TableContainer ref={tableHeight}>

and it looks like this in index.tsx

<div ref={containerHeightRef}>

You will also see that I used useEffect in both <MarketTable /> and index so that I can dynamically set the height of the table as the data changes. Within useEffect in index.tsx we are constantly watching the two refs so that we can adapt to their height and re-render the components involved. That looks like this:

  useEffect(() => {
    if (containerHeightRef && tableHeight) {
      let containerHeight = containerHeightRef.current.clientHeight;
      setSizePercents([
        (tableHeight / containerHeight) * 100,
        ((containerHeight - tableHeight) / containerHeight) * 100
      ]);
    }
  }, [containerHeightRef, tableHeight]);

When you add or remove data from data.tsx you will see that the <Split /> dynamically fits to the table size.

So after we have access to both the table height and the wrapper height within index.tsx, we can now change those pixels to a percent (which is what the sizes prop takes in <Split />). For our final table height percent, we simply divide the tableHeight by the containerHeight (div wrapper height in index.tsx) and multiplying by 100. This gives us the first value within our sizes prop. The split height is next, which is the second percent value for the sizes prop. That is done by ((containerHeight - tableHeight) / containerHeight) * 100.

Now, the final sizes prop looks like this:

      <Split
        className="split"
        direction="vertical"
        minSize={0}
        snapOffset={10}
        dragInterval={5}
        sizes={sizePercents ? [sizePercents[0] + 8.5, sizePercents[1]] : ""}
      >

That additional 8.5 is an offset that takes care of the scrollbar height. Without it, the table height is slightly off (less than what it should be).

By the way, in order to get the tableHeight into index.tsx, we had to send the tableHeight value from <MarketTable /> up the component tree to it's grandparent (index.tsx). We do this by passing settableheight (I had to use lowercase, which I believe is a tyescript issue) like this: index.tsx --> <Market /> --> <MarketTable />. From there, sets the tableHeight and now we have access to it in index.tsx. There is an error regarding the passing of settableheight but it doesn't interfere with the actual functionality. I am sure you can figure that part out.

I know that's a lot, but I have a working solution at the codesandbox link below: https://codesandbox.io/s/confident-black-bn2mi9?file=/src/index.tsx

Again, I am sure there are CLEANER ways to do this in Typescript, but I don't have time to dive into those details. This should give you a great foundation to refactor and help you come up with a solution that is more permanent.

Upvotes: 1

Related Questions