Bootstrap 5 expandable list group

Build Status Gem Version

Bootstrap 5 expandable list group is a Ruby on Rails engine and gives you a simple API for creating expandable and stretchable list groups. A bit like Bootstrap 5's Accordion, Collapse and List group components combined.

https://user-images.githubusercontent.com/7672/120296188-27170580-c2c8-11eb-936a-a93c16326acb.mp4

TL;DR

<%= bs5_expandable_list_group do |c| %>
  <% @posts.each do |post| %>
    <% c.item do |i| %>
      <%= i.title { post.title } %>
      <%= i.body  { post.text } %>
    <% end %>
  <% end %>
<% end %>

Features

  • Choose to show a different title when the item is expanded
  • Show one or more actions (buttons, links, icons, ...) when hovering over an item
  • Choose to show a different set of items when the item is expanded
  • When configured as an accordion only one item can expanded
  • When configured as expandable expanded items are shown a little bit bigger as if they come out a bit.

Prerequisites

  • Stimulus or Hotwire added to your project
  • Bootstrap 5 added to your project. There are different ways to do this, but you should use Yarn with Webpacker.

Installation

Add this line to your application's Gemfile:

gem 'bs5_expandable_list_group'

And then execute:

$ bundle

Run the following command to setup your project to use Bootstrap 5 expandable list group:

$ bin/rails generate bs5_expandable_list_group:install

This copies the required assets to your application directory:

Stylesheets are copied to app/javascript/stylesheets. If needed, you can add the following line to your application.scss to import them:

@import "bootstrap";
@import "stylesheets/expandable-items";
@import "stylesheets/stretchable-items";

Usage

To render a Bootstrap 5 expandable list group you use render(Bs5::ExpandableListGroupComponent.new) and pass it a block for rendering every list item.

Given that you have assigned a list of Post instances to @posts, to render these @posts in a Bootstrap 5 expandable list group, you put the following code in your template:

<%= bs5_expandable_list_group do |c| %>
  <% @posts.each do |post| %>
    <% c.item do |i| %>
      <%= i.title { post.title } %>
      <%= i.body  { post.text } %>
    <% end %>
  <% end %>
<% end %>

This will render a List group showing the title of every post. When you click a title, it will expand the item and the text of the post will be shown.

You are not limited to passing a simple attribute as title or body:

  ...
  <% c.item do |i| %>
    <%= i.title do %>
      <div class="ms-2 me-auto">
        <div class="fw-bold"><%= post.title %></div>
        <%= post.author_name %>
      </div>
      <span class="badge bg-primary rounded-pill"><%= post.comments_count %></span>
    <% end %>
    <%= i.body { ... } %>
  <% end %>

Show different titles

You can show a different title when the item is expanded then when it is collapsed. Use the .collapsed and .expanded methods on the title object:

  ...
  <% c.item do |i| %>
    <%= i.title do |t| %>
      <%= t.collapsed { 'Collapsed title' } %>
      <%= t.expanded { 'Expanded title' } %>
    <% end %>
    <%= i.body { ... } %>
  <% end %>

Show actions

Actions can be used for defining links or buttons that will become visible when hovering over an item and are aligned to the right. When the item is expanded, the actions are permanently visible.

You can choose to either show one set of actions of using a different set when the item is expanded.

To show one set:

  ...
  <% c.item do |i| %>
    <%= i.title do |t| %>
      ...
    <% end %>
    <% i.actions do %>
      <%= link_to 'Delete', post, method: :delete, class: 'btn btn-sm btn-outline-danger' %>
      <%= link_to 'Edit', edit_post_path(post), class: 'btn btn-sm btn-outline-secondary' %>
    <% end %>
    <%= i.body { ... } %>
  <% end %>

To show a different set on hovering:

  ...
  <% c.item do |i| %>
    <%= i.title do |t| %>
      ...
    <% end %>
    <% i.actions do |a| %>
      <% a.collapsed do %>
        <%= link_to 'Delete', post, method: :delete, class: 'btn btn-sm btn-outline-danger' %>
        <%= link_to 'Edit', edit_post_path(post), class: 'btn btn-sm btn-outline-secondary' %>
      <% end %>      
      <% a.expanded do %>
        <%= link_to 'Details', post_path(post), class: 'btn btn-sm btn-outline-secondary' %>
        <%= link_to 'Comment' ... %>
        <%= link_to 'Delete', post, method: :delete, class: 'btn btn-sm btn-outline-danger' %>
        <%= link_to 'Edit', edit_post_path(post), class: 'btn btn-sm btn-outline-secondary' %>
      <% end %>      
    <% end %>
    <%= i.body { ... } %>
  <% end %>

Omitting the item methods

Instead of using the item's methods title, body and/or actions you can just use (HTML-) text as a block.
This will render just a list group item, but will come in handy when you, i.e. show the first 25 items of a longer list and want to present a "Load more" link as last item of the list (for simplicity sake the sample uses a non-working link_to_next_page although this is part of Kaminari):

<%= bs5_expandable_list_group do |c| %>
  <% @posts.each do |post| %>
    <% c.item do |i| %>
      <%= i.title { post.title } %>
      <%= i.body  { post.text } %>
    <% end %>
  <% end %>
  <% if @posts.next_page %>
    <% c.item do %>
      <%= link_to_next_page @posts "Load more" %>
    <% end %>
  <% end %>
<% end %>

Wrap an item in an extra element

Every list item is a div with one or more CSS classes and, dependent on block passed, some data attributes and is not customizable (yet).
However, you can wrap a list item in a extra element by passing a wrapper_html option to the item method:

  ...
  <% c.item(wrapper_html: { tag: :turbo_frame, id: dom_id(post) }) do |i| %>
    <%= i.title do |t| %>
      <%= t.collapsed { 'Collapsed title' } %>
      <%= t.expanded { 'Expanded title' } %>
    <% end %>
    <%= i.body { ... } %>
  <% end %>

All options passed to the item method are used as HTML attributes of the wrapper, expect for the following:

name default description
tag :div A symbol or string of a valid HTML tag that is used for the tag of the wrapper

Passing options

The following options can be passed to Bs5::ExpandableListGroupComponent.new:

name default description
tag :div A symbol or string of a valid HTML tag that is used for the tag of the list group
id is used to assign an id HTML attribute to the rendered container
class is added to the class attribute of the rendered container
accordion false Behaves as an Bootstrap accordion by having only 1 item expanded
expandable false Expanded items are shown a little bit bigger as if they come out a bit

Example:

<%= bs5_expandable_list_group(accordion: true, expandable: true) do |c| %>
  ...
<% end %>

Other options are used as HTML attributes of the list group element.

Running tests

$ bin/rails app:ci

License

The gem is available as open source under the terms of the MIT License.