noblerare
noblerare

Reputation: 11863

React - Absolutely positioning elements and drawing lines

I am working on a React app in which there is a section of behavior which should behave as follows. There are a list of steps (which are collapsible and expandable). There is ability for the user to select the "Start Loop" link after which some selectable circles appear for the user to select which step they want to loop back to. Upon selecting that step, there should be a curve that is drawn to indicate that that connection has been made.

Here is a diagram to show how I want it to be:

enter image description here

Currently, I am able to get steps 1 and 2 working but am lost at how to achieve step 3. Here is a React fiddle to show you what I have so far.

https://jsfiddle.net/xaz7c6Le/7/

I have two issues:

1) If you look at the CSS for startMarker and clickCircle, you'll notice that I am trying to absolutely position those elements. However, with some browser window resizing, it is evident that that does not work. How do I get those elements to always rename vertically centered within the step div it is a part of?

  1. How do I draw the line connecting one step to another? If I add another step between steps 2 and 3, this line should elongate to still connect steps 1 and 5.

Just FYI, my question is about the behavior, alignment of elements, and drawing of lines, NOT on how well the JSFiddle React code is written. (The real application is quite extensive and adheres to React best practices but to get something to show on the JSFiddle, I just threw something together).

My code is below:

class LoopApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
        showCircles: false,
        selectedCircleIndex: -1,
        steps: [
        { title: "Step 1", partOfLoop: false },
        { title: "Step 2", partOfLoop: false },
        { title: "Step 3", partOfLoop: false },
        { title: "Step 4", partOfLoop: false }
      ]
    }
  }

  showCircles() {
    this.setState({ showCircles: true });
  }

  selectCircle(idx) {
    this.setState({ selectedCircleIndex: idx });
  }

  render() {
    return (
      <div>
          <h2>Block A:</h2>
          <div class="inner-block">
              {
                  this.state.steps.map((step, index) => 
                      <div class="step-entry">
                          {index + 1}. {step.title}

                          {
                              this.state.showCircles &&
                                <div
                                    className={"clickCircle " + (this.state.selectedCircleIndex === index ? "selected" : "" )}
                                    onClick={() => this.selectCircle(index)}></div>
                          }
                      </div>
                  )
              }
              <div class="step-entry">
                  5. Step 5

                  <a
                      class="start-loop"
                      onClick={() => this.showCircles()}>Start loop
                  </a>

                  {
                      this.state.showCircles && <div class="startMarker"></div>
                  }
              </div>
          </div>
      </div>
    )
  }
}

ReactDOM.render(<LoopApp />, document.querySelector("#app"))

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

.inner-block {
    margin-top: 10px;
    border: 1px black solid;
    width: 30%;
}

.step-entry {
    padding: 20px;
    position: relative;
    border-bottom: 1px solid black;
}

.clickCircle {
    position: absolute;
    border: 1px solid blue;
    border-radius: 50%;
    height: 20px;
    width: 20px;
    left: 97%;
    top: 18px;
    background-color: white;   
}

.clickCircle:hover {
    background-color: orange;
}

.selected {
    background-color: orange;
}

.start-loop {
    float: right;
    color: orange;
}

.start-loop:hover {
    cursor: pointer;
    text-decoration: underline;
}

.startMarker {
    position: absolute;
    width: 20px;
    height: 10px;
    background-color: orange;
    top: 25px;
    left: 97%;
}

Upvotes: 1

Views: 1791

Answers (1)

khan
khan

Reputation: 1464

95% working solution on JSFiddle


To answer your questions:

  1. For your code:
<div className="step-entry">
    <div className="startMarker"/>
</div>

You can implement the following CSS:

.step-entry {
    display: flex;
    align-items: center;
}

This will align the children of .step-entry vertically in relation to the .step-entry element.

Unfortunately, I do not have an answer to horizontally aligning .startMarker.


  1. One possible solution to the line connecting two circles is to have a box behind your .step-entry elements and offset it so that its right section is sticking out to the right of your Logo App.

    You can then manipulate the height that underlying box to match the distance between the selected circle and the last circle.

    I was able to work out the calculations in an onClick event handler set on the circles.

Upvotes: 3

Related Questions