0

I am trying to design a custom accordion with these fancy red lines connecting parent and children (see photo).

enter image description here

I am using Grommet components here but in summary, the layout it's just a bunch of divs (the Box tag) and a collapsible panel component for the children(the Collapsible tag). Children panels can be opened and show more content.

After a couple of tries, what I did to connect parent and children is to wrap the outer box with a left-border and then remove the extra border on the bottom using a white box on top of it (the Stack tag of the second example code). The horizontal line connecting the card and the left-border is just a styled div placed next to the Child tab ( inside the SubMenuElement component ).

I think this is quite an intricate solution ( also because I need to make the white box responsive ) but I couldn't think about a simpler one. Do you have any suggestion on how can I improve or re-do the red connections? Thanks in advance for the help! Please note that I am aware all the panels are using the same variables on click

 <MenuButton
    open={openMenu}
    label="PARENT TAB-XYZ"
    onClick={() => {
      const newOpenMenu = !openMenu;
      setOpenMenu(newOpenMenu);
      setOpenSubmenu1(!newOpenMenu ? false : openSubmenu1);
    }}
  />
  <Collapsible open={openMenu}>
    <Box background="antiquewhite" margin={{ left: 'small' }} border={{ side: 'left', size: '2px', color: 'red' }}>
      {Tabs.map(el => {
        return (
          <SubMenuElement
            key={el.title}
            open={openSubmenu1}
            label={el.title}
            onClick={() => setOpenSubmenu1(!openSubmenu1)}
          />
        );
      })}
    </Box>
  </Collapsible>

 <MenuButton
    open={openMenu}
    label="PARENT TAB-POU"
    onClick={() => {
      const newOpenMenu = !openMenu;
      setOpenMenu(newOpenMenu);
      setOpenSubmenu1(!newOpenMenu ? false : openSubmenu1);
    }}
  />
  <Collapsible open={openMenu}>
    <Stack anchor="bottom-left">
      <Box
        background="antiquewhite"
        margin={{ left: 'small' }}
        border={{ side: 'left', size: '2px', color: 'red' }}
      >
        {Tabs.map(el => {
          return (
            <SubMenuElement
              key={el.title}
              open={openSubmenu1}
              label={el.title}
              onClick={() => setOpenSubmenu1(!openSubmenu1)}
            />
          );
        })}
      </Box>
      <Box background="white" height="39px" width="35px"></Box>
    </Stack>
  </Collapsible>
</Box>

);

Ardenne
  • 897
  • 2
  • 13
  • 23

2 Answers2

1

Based on one of my answer for someone who needed a divider, I can propose you something like that: repro on Stackblitz

You will find the original divider code on the first link. for your needs, i modified it a little so it just add the link on the left of the content. There is still a border-left on the content wrapper tho, it seems the easiest solution for me.

Your main file :

import React, { Component } from "react";
import { render } from "react-dom";
import Divider from "./divider";
import "./style.css";

const App = () => {
  const toggleAccordion = e => {
    e.target.classList.toggle("hidden");
  };

  return (
    <>
      <div className="accordion hidden" onClick={toggleAccordion}>
        accordion header
        <div className="accordion-content-wrapper">
          <Divider>
            <div>Content</div>
          </Divider>
          <Divider>
            <div>Content</div>
          </Divider>
          <Divider>
            <div>Content</div>
          </Divider>
        </div>
      </div>
    </>
  );
};

render(<App />, document.getElementById("root"));

My accordion css (your component already have this feature i guess, i just made a minimal reproduction):

.accordion.hidden {
  height: 18px;
  overflow: hidden;
}

.accordion-content-wrapper{
  margin-left: 10px;
  border-left: 1px solid black;
}

And for the divider, there not a lot of change from my original answer, here is the code:

import React from 'react';

const Divider = ({ children }) => {
  return (
    <div className="divider-component">
      <div className="container">
        <div className="border" />
        <span className="content">{children}</span>
      </div>
    </div>
  );
};

export default Divider;

css:

.divider-component .container{
  display: flex;
  align-items: center;
}

.divider-component .border{
  border-bottom: 1px solid black;
  width: 15px;
}

.divider-component .content {
  width: 100%;
}

Even if you'll have to edit it to fit your needs, the idea is to add a flex container so you can add the little link on the left of your content, correctly aligned with your content.

Quentin Grisel
  • 4,794
  • 1
  • 10
  • 15
  • Thank you for your help! Your answer has been very helpful to come up with a solution. I ended up doing something similar using two flexboxes instead of the divider. – Ardenne May 12 '20 at 13:16
0

For whoever will encounter the same issue, I ended up using two 50% height flexboxes inside a div that replace the horizontal line. This allows managing the responsive resize automatically while giving flexibility on the last item border.

    const SubMenuElement = ({ last, label, open, onClick }: { last?: boolean; label: string; open: any; onClick: any }) => {
  return (
    <Box direction="row">
      <Line last={last} />
      <Box width="100%" margin={{ vertical: 'small' }}>
        <Card background="white" onClick={onClick}>
          ....
        </Card>
      </Box>
    </Box>
  );
};

Where the Line is

   const Line = ({ last }: { last?: boolean }) => (
  <Box direction="column" width="20px" flex="grow">
    <Box height="50%" style={{ borderLeft: '1px solid red', borderBottom: '1px solid red' }} />
    <Box height="50%" style={last ? {} : { borderLeft: '1px solid red' }} />
  </Box>
);
Ardenne
  • 897
  • 2
  • 13
  • 23