Search code examples
htmlcssangularionic-frameworkcss-grid

Place div below another div with property display:grid


I'm having issues trying to put a div below another div with diplay:grid. The div is being placed on top of the other one and not below.

"app-bm-payment-card-item" is a custom Angular component.

HTML file

//div wrapper
<div *ngIf="pageReady && myProfiles" class="ion-padding">

//div showing the cards stacked or collapsed
<div *ngIf="!inEditMode" class="card_container_stacked_main">
  <app-bm-payment-card-item *ngFor="let card of myProfiles" [card]="card" [editMode]="inEditMode"
    (onDeleteClick)="removeCard(card)" (onSelect)="selectCard(card)" (onEditClick)="editCardInfo(card)">
  </app-bm-payment-card-item>
</div>

//div showing the cards expanded
<div *ngIf="inEditMode" class="card_container_expanded_main">
  <app-bm-payment-card-item *ngFor="let card of myProfiles" [card]="card" [editMode]="inEditMode"
    (onDeleteClick)="removeCard(card)" (onSelect)="selectCard(card)" (onEditClick)="editCardInfo(card)">
  </app-bm-payment-card-item>
</div>

//div to be placed below the other one(s)
<div class="div">
  TEXT
</div>

</div>

SCSS file:

.card_container_stacked_main {
display:grid;
position: relative;
grid-auto-rows:30px; /* a fixed height to create an overflow */ 
}

.card_container_expanded_main {
position: relative;
max-width: 400px !important;
//margin-bottom: 15px;
}

.div {
position: relative;
}

So, the basic functionality is when tapping on any card the card list should be display stacked or collapsed. That's why I'm using the inEditMode boolean, to get to know which div should be displayed and which one hidden. When the expanded card list is displayed the div (TEXT) is being placed below the list correctly, but when the stacked card list is activated the div is being placed on top of the list, which is wrong, it should be placed below the stacked/collapsed card list. See the images below.

Stacked card list

Collapsed card list


Solution

  • Interesting problem, because the number of cards in the "wallet" can be 1, 2 or N and the fact that grid-auto-rows and/or grid-template-rows need to have tracking size explicit specified in the CSS.

    Multiple solutions available (conserving the use of grid)

    enter image description hereenter image description here

    #1 The easy but ugly one

    Simply set a margin-top: 200px; to the TEXT div. You apply this only when the cards are stacked.

    I'm not a big fan of this solution since is more a "fix" than a proper design.

    function expandStackCards() {
      var wrapper = document.getElementById("wrapper");
      if (wrapper.classList.contains("stacked")) {
        wrapper.classList.remove("stacked");
      } else {
        wrapper.classList.add("stacked");
      }
    }
    button {
      width: 400px;
      margin-bottom: 10px;
    }
    #wrapper {
      display: grid;
      grid-template-columns: 400px;
      gap: 10px;
    }
    #wrapper.stacked  {
      grid-auto-rows: 30px;
      gap: 0px;
    }
    .card {
      width: 100%;
      height: 200px;
      background-color: grey;
      border-radius: 20px;
      padding: 6px 20px;
      box-sizing: border-box;
    }
    .card span {
      font-family: sans-serif;
      text-transform: uppercase;
      color: white;
    }
    .card.green {
      background-color: green;
    }
    .card.blue {
      background-color: blue;
    }
    .my-text {
      margin: 10px;
    }
    .stacked .my-text {
      margin-top: 200px;
    }
    <button onclick="expandStackCards()">Expand / Stack</button>
    <div id="wrapper" class="stacked">
      <div class="card green"><span>Card #1</span></div>
      <div class="card blue"><span>Card #2</span></div>
      <div class="my-text">TEXT</div>
    </div>

    Or find here the Codepen code

    #2 Using JavaScript to detect and set properly the grid-auto-rows value

    In my opinion the best solution since there is no margin trick, only pur grid CSS.

    To achieve that you need a bit of JS to detect the number of cards in the wallet, then set the value.

    function stackedCards() {
      var wrapper = document.querySelector('#wrapper.stacked');
      if (wrapper) {
        var num_cards = wrapper.querySelectorAll(':scope .card').length;
    
        let style = getComputedStyle(wrapper);
        row_height_str = style.gridAutoRows;
    
        var new_grid_auto_rows = row_height_str.concat(' ').repeat(num_cards - 1);
    
        new_grid_auto_rows = new_grid_auto_rows.concat('auto');
    
        wrapper.style.gridAutoRows = new_grid_auto_rows;
      }
    }
    
    // This function is only for the DEMO button
    function expandStackCards() {
      var wrapper = document.getElementById("wrapper");
      if (wrapper.classList.contains("stacked")) {
        wrapper.classList.remove("stacked");
        wrapper.style.removeProperty("grid-auto-rows");
      } else {
        wrapper.classList.add("stacked");
        stackedCards();
      }
    }
    
    stackedCards();
    button {
      width: 400px;
      margin-bottom: 10px;
    }
    #wrapper {
      display: grid;
      grid-template-columns: 400px;
      gap: 10px;
    }
    #wrapper.stacked {
      grid-auto-rows: 30px;
      gap: 0px;
    }
    .card {
      width: 100%;
      height: 200px;
      background-color: grey;
      border-radius: 20px;
      padding: 6px 20px;
      box-sizing: border-box;
    }
    .card span {
      font-family: sans-serif;
      text-transform: uppercase;
      color: white;
    }
    .card.green {
      background-color: green;
    }
    .card.blue {
      background-color: blue;
    }
    .my-text {
      margin: 10px;
    }
    <button onclick="expandStackCards()">Expand / Stack</button>
    <div id="wrapper" class="stacked">
      <div class="card green"><span>Card #1</span></div>
      <div class="card blue"><span>Card #2</span></div>
      <div class="my-text">TEXT</div>
    </div>

    Or find here the Codepen code

    #3 Using JavaScript to set a class to the #wrapper

    You can also use JS to detect the number of cards, then apply the right class to the grid (#wrapper).

    It's a bit like the previous solution but without applying CSS style via JS, but of course for this you will need to have in your CSS file something like that :

    .wallet-1-cards {
      grid-auto-rows: auto;
    }
    .wallet-2-cards {
      grid-auto-rows: 30px auto;
    }
    .wallet-3-cards {
      grid-auto-rows: 30px 30px auto;
    }
    .wallet-4-cards {
      grid-auto-rows: 30px 30px 30px auto;
    }
    
    // etc...
    
    

    With this third solution you avoid doing CSS in your JS code (you only set a class to a div), which is not a bad solution only if you know that it's impossible to have more that like 5 or 6 cards, because polluting your CSS with dozens of useless definitions is not a good thing.