What is the best way to create an arrow connecting a parent div to multiple child divs?

I'm faced with a challenge involving a reusable React list component that supports nested children. My goal is to visually connect the parent div to its direct children using arrows, similar to the image linked below.

View List Component Image

Below is an illustration of a nested list component in action:

import React from 'react';
import ListElement from './ListElement.js';

const List = () => (
  <>
    <ListElement>
      <ListElement>
        <ListElement>
          <ListElement />
          <ListElement>
            <ListElement />
          </ListElement>
        </ListElement>
        <ListElement />
      </ListElement>
      <ListElement />
    </ListElement>
    <ListElement />
  </>
);

export default List;

The structure of the ListElement component is as follows:

    import React from 'react';

    const ListElement = props => {
      const indentationStyle = { paddingLeft: `${3 * props.indent}rem`,
      position: 'relative'};

      const lineStyle = {
        left: `${2 + 3 * (props.indent - 1.2)}rem`,
      };

      const tile = (
        <div style={indentationStyle}>
          {props.indent > 0 ? (
            <div className={'arrow-line-container'} style={lineStyle}>
              <div className={'arrow-line'}/>
              <div className={'curve-arrow-line'}/>
            </div>
          ) : null}
          <div
            style={{
              border: '1px solid black',
              padding: '1rem',
              marginBottom: '1rem',
            }}
          >
            I am a ListElement
          </div>
        </div>
      );

      const getChildren = () => {
        let elements = React.Children.toArray(props.children);

        // increase indent prop of each child and assign what number child it is in the list
        elements = elements.map((element, index) => {
          return React.cloneElement(element, {
            ...element.props,
            indent: props.indent + 1,
            childNumber: index,
          });
        });
        return elements;
      };

      const childTiles = <div className={'child-tile'}>{getChildren()}</div>;

      const arrowStyle = {
        backgroundPosition: `${1.3 + 3 * (props.indent - 1)}rem`,
      };

      return (
        <>
          <ul className={'no-bullet'}>
            <li
              className={props.indent === 0 ? 'no-arrow' : 'arrow'}
              style={arrowStyle}
            >
              {tile}
            </li>
            {props.children ? childTiles : null}
          </ul>
        </>
      );
    };

    ListElement.defaultProps = {
      childNumber: 0,
      indent: 0,
    };

    export default ListElement;

Here's how the CSS styling for this functionality looks like:

ul.no-bullet {
  list-style-type: none;
  padding: 0;
  margin: 0;
}

.arrow-line {
  border-left: 2px solid #6a6969;
  content: "";
  position: absolute;
  height: 65%;
}

li.arrow {
  background: url("./arrow.png") no-repeat;
}

li.no-arrow {
  display: block;
}

Currently, I have been using <li> elements for the list and replacing the bullet points with arrow images. The main struggle lies in accurately calculating the line height and top position for better visual alignment between elements. Any recommendations or insights on this issue would be greatly appreciated.

You can also check out the Plunker demo here.

Answer №1

After some experimentation, I was able to come up with a clever fix that involves drawing just one line at the end child while utilizing offsetTop and getBoundingClientRect().height to determine the arrow-line's position and height. You can view the successful implementation here: https://plnkr.co/edit/SFzgiZi1dckRa79C

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Managing JavaScript expiration time in API responses

Seeking help with a JavaScript API I'm new to. The response I received contains a timestamp, which seems like it's in milliseconds. I want to format this time for a countdown but I'm not sure what these milliseconds are referring to. I know ...

Error: The storage module could not be located in the 'firebase/app' package when imported as 'firebase'

Encountered an error when attempting to import Firebase storage: Firebase storage: The 'storage' export (imported as 'firebase') was not found in 'firebase/app' This is my app code: import * as firebase from "firebase/ap ...

What could be the reason behind the frequent occurrence of the "ECONNRESET" error in React?

While attempting to create a React app using 'npx create-react-app my-app', I encountered an npm ECONNRESET error. Being new to ReactJS, I am unsure of how to resolve this issue and would appreciate any assistance provided. I have decided to seek ...

Using JavaScript parameters in a HTML document

I am trying to replicate a page similar to this. The issue I am facing is the inability to use external JS files in ASP.net (as far as I know). Therefore, I am defining the functions and attempting to utilize them within the HTML page instead. <%@ P ...

Obtaining JSON data in a separate JavaScript file using PHP

I have an HTML file with the following content: // target.html <html xmlns="http://www.w3.org/1999/xhtml"> ... <script src="../../Common/js/jquery-ui-1.10.3.js"></script> <script src="../../Common/js/select.js" type="text/javascript"& ...

Releasing Typescript 2.3 Modules on NPM for Integration with Angular 4

Although there are instructions available in Writing NPM modules in Typescript, they are outdated and there are numerous conflicting answers that may not be suitable for Angular. Additionally, Jason Aden has delivered an informative presentation on youtu ...

Validation Failure: Every child within the list must be accompanied by a distinct "key" prop

I struggled to solve this error for a long time. I made sure to add the key key={models.model_id} in that map function. This is how it looks on my form: <JSelect label="Model" labelId="assembly_model_id" name="prod ...

Learn how to toggle the visibility of a gif image with a button click in an ASP.NET application

I am working on an asp page that includes a button. When the button is clicked, I need to display a gif image. Once the process is complete, the image should be hidden again. Here is the code behind: <head runat="server"> <title>Untitled ...

When the form is submitted, the text input vanishes and then the page is refreshed using AJAX technology

Can anyone help me troubleshoot why my page is reloading even though I used AJAX, and how to prevent it from clearing my input text after clicking the submit button? I tried using the show method to resolve this issue but it didn't work. <form met ...

What is the process for configuring the global warning level in ESLint?

My current setup involves using WebStorm and the ESLint configuration provided by Airbnb. I'm curious if there is a way to have ESLint errors displayed in a more subtle manner instead of the harsh red color, or perhaps make all ESLint rules warnings ...

The switchMap function is sending back a single item

I'm having an issue with switching the observable using the switchMap operator: return this.db.list(`UserPlaces/${this.authData.auth.auth.currentUser.uid}`, { query: { orderByChild: 'deleted', equalTo: false } }) .ma ...

When a radio button is checked, add a class to its parent element that has a specific class assigned to it

In order to dynamically add a class to a specific div element higher up the DOM hierarchy when a radio button is clicked, I am in need of assistance. There are multiple instances of these div elements with different radio buttons, so it is crucial that on ...

What is the reasoning behind declaring certain variables on the same line as others, while most are declared individually on separate lines?

I've taken on the challenge of learning JS by myself and decided to build a Blackjack game. While following a helpful walkthrough online, I encountered some confusion. On the website, they start by declaring Global variables: var deck; var burnCard; ...

Cannot use MaterialUI Textfield/Input on iPhone devices

Recently, I encountered an issue with iPhone users being unable to type in Textfield or Input components in apps developed using MaterialUI, even when the value and setValue were properly configured. To solve this problem for each component individually, ...

I am looking to adjust the height of my MUI Grid component

Recently exploring React, and I'm looking to set a height limit for MUI Grid. I've structured my project into 3 sections using MUI grid components. My goal is to restrict the page height in order to eliminate the browser scrollbar. If the conten ...

Assistance required: The database is not found upon page reload. How can this be resolved?

My HTML5 code has created a database with create, add, delete, and print operations. However, the ObjectStore is re-created every time I load the page, and the additional values stored are not present upon reloading the page. What could be wrong with my ...

There seems to be a problem with create-react-app as it is not being recognized as a valid cmdlet, function, script file, or program

I currently have Node(v6.10.3) and npm(3.10.10) installed on my computer system. After successfully installing create-react-app, I attempted to create a new project by running the command: create-react-app sample-app. However, an error message appeared: ...

What is the best way to create a vertical connector line between Material UI icons in a list of list items?

How can I add a vertical connector line between avatar images in a React Material UI list item icons? Please see the sandbox URL for reference: https://codesandbox.io/s/9z2x527y6r [Please refer to the attached image below for an example of the desired ve ...

Express.js Passport.js throws an error when req.user is not defined

The middleware function below is unable to access req.user or determine if the user is logged in after they log in. I have confirmed that passport.serializeUser is successful after logging in and that req is defined when accessed from the middleware funct ...

Execute command problem

Explaining this code may be a bit tricky, but I'll do my best. Below is the code snippet for executing a slash command. client.on('interactionCreate', async interaction => { if (!interaction.isCommand()) return; const command = c ...