Search code examples
javascripthtmlcssreactjsweb-component

How to set same dynamic width of component child for all instances? Web Components


Assume I have a small component in a web application that represents an expander control, i.e. a header text, an icon to expand / collapse and some content.

A very simple React implementation (pseudo code) could look like this:

const Expander = ({children, title, onExpandToggle, isExpanded}) => (
  <div>
    <div><span>{title}</span><img src={...} onClick={onExpandToggle} /></div>
    {isExpanded && children}
  </div>
);

This implementation shows the icon after the title, so the position of the icon is determined by the length of the title.

It could look something like this:
Example

Now assume that there are multiple like this below each other. It becomes messy:
Example messy

To make this cleaner, all icons should have the same padding from left. The padding should not be fixed but dynamic, so that the longest title determines the position of all icons:
Example clean

Assuming that I want to keep the expander in its own component, is there a CSS way to achieve my goal?

So far, I haven't tried anything as I don't have a starting point. In WPF I would have used something like SharedSizeGroup, but this doesn't exist for CSS.


Solution

  • Assuming you'll have a container to your component, you can set display: flex to the inner container and align-self: flex-end to your image.

    Then wrap your component/s with a div that has display: inline-block which takes the width of the biggest element inside.

    Here's an example:

    .container{
      display: inline-block;
      padding: 3px;
    }
    
    .item{
      display: flex;
      flex-direction: row; 
      justify-content: space-between;
    }
    
    .item .plus{
      width: 15px;
      height: 15px;
      background-image: url("https://cdn1.iconfinder.com/data/icons/mix-color-3/502/Untitled-43-512.png");
      background-size: cover;
      background-repeat: no-repeat;
      align-self: flex-end;
      margin-left: 10px;
    }
    <div class="container">
      <div class="item">
        <div>Synonyms</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div>Concept</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div>Term</div>
        <div class="plus"></div>
      </div>
    </div>

    Affecting all the instances without a shared container or fixed width and with CSS alone, is not possible, since there is no way to access a parent element with CSS. Therefore, If you'll have an inner instance (the biggest one) it won't be able to apply it's width to its own parent or any ancestor and it's other children.

    If you're after a solution that will set all the instances in the page with the same size without them sharing a container you can achieve it with JS.

    Calculate the width of each instance, save the biggest, then set this width for the rest of the instances. In this example I'm also highlighting the biggest item. The items are all around the page and can be inside various divs and displays or without any container.

    var biggestWidth = 0;
    var biggestItem;
    
    function setWidth() {
      $(".item").each(function() {
        var currentWidth = $(this).width();
        if (currentWidth > biggestWidth) {
          biggestWidth = Math.ceil(currentWidth);
          biggestItem = $(this);
        }
      });
    
      $(".item").width(biggestWidth);
      biggestItem.addClass("biggest");
    }
    
    $(setWidth());
    section {
      width: 40%;
      float: left;
      border: 1px solid black;
      border-radius: 3px;
      margin: 10px;
      padding: 10px;
    }
    
    .s1 {
      background-color: #e5e5e5;
      display: table;
    }
    
    .item {
      display: inline-block;
      clear: both;
      float: left;
    }
    
    .txt {
      float: left;
      display: inline-block;
    }
    
    .plus {
      width: 15px;
      height: 15px;
      background-image: url("https://cdn1.iconfinder.com/data/icons/mix-color-3/502/Untitled-43-512.png");
      background-size: cover;
      margin-left: 10px;
      float: right;
    }
    
    .shift{
      margin-left: 30%;
    }
    
    .clear{
      clear: both;
    }
    
    .biggest{
      background-color: yellow;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <section class="s1">
      <div class="item">
        <div class="txt">Synonyms</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div class="txt">Concept</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div class="txt">Term</div>
        <div class="plus"></div>
      </div>
    </section>
    <section>
      <div class="item">
        <div class="txt">Synonyms</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div class="txt">Concept</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div class="txt">Term</div>
        <div class="plus"></div>
      </div>
    </section>
    
    <section>
      <div class="item">
        <div class="txt">Synonyms - Long</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div class="txt">Concept</div>
        <div class="plus"></div>
      </div>
      <div class="item">
        <div class="txt">Term</div>
        <div class="plus"></div>
      </div>
    </section>
    
    <div class="item">
      <div class="txt">Synonyms</div>
      <div class="plus"></div>
    </div>
    <div class="item">
      <div class="txt">Concept</div>
      <div class="plus"></div>
    </div>
    <div class="item">
      <div class="txt">Term</div>
      <div class="plus"></div>
    </div>
    
    <div class="clear"></div>
    <div class="shift">
    <div class="item">
      <div class="txt">Synonyms</div>
      <div class="plus"></div>
    </div>
    <div class="item">
      <div class="txt">Concept</div>
      <div class="plus"></div>
    </div>
    <div class="item">
      <div class="txt">The bigget item is here</div>
      <div class="plus"></div>
    </div>
    </div>