Skip to content

Commit

Permalink
adds responsive tabs styling (#46)
Browse files Browse the repository at this point in the history
* adds responsive tabs styling

* removes js from library

---------

Co-authored-by: “Kerry <“kmurphychi@gmail.com”>
  • Loading branch information
kmurphychi247 and “Kerry authored Jun 6, 2024
1 parent 10c90c5 commit 8ec6ced
Show file tree
Hide file tree
Showing 7 changed files with 337 additions and 10 deletions.
31 changes: 31 additions & 0 deletions assets/js/tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
document.addEventListener('DOMContentLoaded', function () {
const selectElement = document.getElementById('nav-tabs-mobile-js');
const tabButtons = document.querySelectorAll('[data-bs-target]');

selectElement.addEventListener('change', function () {
const selectedTab = selectElement.value;
const tabTrigger = document.querySelector(`button[data-bs-target="${selectedTab}"]`);
if (tabTrigger) {
new bootstrap.Tab(tabTrigger).show();
}
});

tabButtons.forEach(button => {
button.addEventListener('click', function () {
const target = button.getAttribute('data-bs-target');
const tabTrigger = document.querySelector(`button[data-bs-target="${target}"]`);
if (tabTrigger) {
new bootstrap.Tab(tabTrigger).show();
}
});
});

// Optional: Change the select dropdown when a tab is clicked
document.querySelectorAll('#myTab button').forEach(button => {
button.addEventListener('shown.bs.tab', function (e) {
const target = e.target.getAttribute('data-bs-target'); // Get the data-bs-target attribute
selectElement.value = target; // Set the select value
});
});
});

43 changes: 43 additions & 0 deletions assets/scss/custom/_overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,46 @@ em.placeholder {
lite-youtube {
max-width: 100%;
}

// Vertical Tabs
.vtabs {
@include media-breakpoint-up(lg) {
display: grid;
grid-template-columns: 50% 50%;
grid-gap: 1rem;
}

.accordion-item {
border: none;
}

.nav-tabs .nav-link {
width: 100%;
text-align: left;
border-radius: 0.375rem;

&.active {
border-bottom: #dee2e6 1px solid;
}
}

ul.nav-tabs.nav {
display: block;
border-bottom: none;
}
}

.nav.nav-tabs.nav-tabs-mobile {
display: flex;
width: 100%;
background-color: #fff;
padding: 0.625rem 2.25rem 0.625rem 0.625rem;
margin-bottom: 1.25rem;
margin-top: 1rem;
font-size: 1rem;
font-weight: normal;
line-height: 130%;
border-radius: 3px;
border: 1px solid #000;
cursor: pointer;
}
1 change: 1 addition & 0 deletions saplings_child.libraries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ form:
am:
js:
dist/js/js/am.js: { minified: true }

Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
{# Check if the field is part of a paragraph #}
{% if element['#object'] is defined %}
{# Get the parent paragraph entity #}
{% set parent_paragraph = element['#object'].id.value %}
{% endif %}
{% set pane_id_base = "pane-" ~ random() %}

{% set tabs = [] %}
{% for item in items %}
{% set tab = pattern('nav_item', {
<div class="d-lg-block d-none">
{% set tabs = [] %}
{% for item in items %}
{% set tab = pattern('nav_item', {
link: item.content|merge({'#view_mode': 'sa_tab_item'}) ,
toggle: pane_id_base ~ '-' ~ loop.index,
toggle: parent_paragraph ~ '-' ~ loop.index,
active: loop.first
}) %}
{% set tabs = tabs|merge([tab])%}
{% endfor %}
{{ pattern('nav', {
{% set tabs = tabs|merge([tab])%}
{% endfor %}
{{ pattern('nav_tabs', {
items: tabs
}, 'tabs') }}
}, 'tabs nav-tabs-responsive') }}

</div>

<div class="tab-content">
{% for item in items %}

{% set item = loop.first ? item.content|add_class(["show", "active"]) : item.content %}
{{ item|add_class("tab-pane")|set_attribute("id", pane_id_base ~ '-' ~ loop.index) }}
{{ item|add_class("tab-pane")|set_attribute("id", parent_paragraph ~ '-' ~ loop.index) }}
{% endfor %}
</div>
139 changes: 139 additions & 0 deletions templates/overrides/paragaphs/paragraph--sa-tabs.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{#
/**
* @file
* Default theme implementation to display a paragraph.
*
* Available variables:
* - paragraph: Full paragraph entity.
* Only method names starting with "get", "has", or "is" and a few common
* methods such as "id", "label", and "bundle" are available. For example:
* - paragraph.getCreatedTime() will return the paragraph creation timestamp.
* - paragraph.id(): The paragraph ID.
* - paragraph.bundle(): The type of the paragraph, for example, "image" or "text".
* - paragraph.getOwnerId(): The user ID of the paragraph author.
* See Drupal\paragraphs\Entity\Paragraph for a full list of public properties
* and methods for the paragraph object.
* - content: All paragraph items. Use {{ content }} to print them all,
* or print a subset such as {{ content.field_example }}. Use
* {{ content|without('field_example') }} to temporarily suppress the printing
* of a given child element.
* - attributes: HTML attributes for the containing element.
* The attributes.class element may contain one or more of the following
* classes:
* - paragraphs: The current template type (also known as a "theming hook").
* - paragraphs--type-[type]: The current paragraphs type. For example, if the paragraph is an
* "Image" it would result in "paragraphs--type--image". Note that the machine
* name will often be in a short form of the human readable label.
* - paragraphs--view-mode--[view_mode]: The View Mode of the paragraph; for example, a
* preview would result in: "paragraphs--view-mode--preview", and
* default: "paragraphs--view-mode--default".
* - view_mode: View mode; for example, "preview" or "full".
* - logged_in: Flag for authenticated user status. Will be true when the
* current user is a logged-in member.
* - is_admin: Flag for admin user status. Will be true when the current user
* is an administrator.
*
* @see template_preprocess_paragraph()
*
* @ingroup themeable
*/
#}
{%
set classes = [
'paragraph',
'paragraph--type--' ~ paragraph.bundle|clean_class,
view_mode ? 'paragraph--view-mode--' ~ view_mode|clean_class,
not paragraph.isPublished() ? 'paragraph--unpublished',
'paragraph--id--' ~ paragraph.id.value,
]
%}
{% set verticaltabs = 0 %}
{% if content.sa_display_as_vertical_tabs['#items'].value == 1 %}
{% set verticaltabs = true %}
{% endif %}

{% set vtabs = '' %}
{% if verticaltabs %}
{% set vtabs = ' vtabs' %}
{% endif %}

{% set container_class_string = 'container' %}
{% if paragraph.sa_container.value is not empty %}
{% set container_class_string = ' ' ~ paragraph.sa_container.value %}
{% endif %}

{% set width_class_string = '' %}
{% if paragraph.sa_width.value is not empty %}
{% set width_class_string = ' ' ~ paragraph.sa_width.value %}
{% endif %}

{% set margin_class_string = '' %}
{% if paragraph.sa_margin.value is not empty %}
{% set margin_class_string = ' ' ~ paragraph.sa_margin.value %}
{% endif %}

{% set padding_class_string = '' %}
{% if paragraph.sa_padding.value is not empty %}
{% set padding_class_string = ' ' ~ paragraph.sa_padding.value %}
{% endif %}

{% set bg_class_string = '' %}
{% if paragraph.sa_background.value is not empty %}
{% set bg_class_string = ' ' ~ paragraph.sa_background.value %}
{% endif %}

{{ attach_library('saplings_child/tabs') }}

{% block paragraph %}
<div{{attributes.addClass(classes)}}>
<div class="{{ container_class_string }}{{ bg_class_string }}{{ margin_class_string }}{{ padding_class_string }}">
{% if paragraph.sa_width.value is not empty %}
<div class="row justify-content-center">
<div class="{{ width_class_string }}">
{% endif %}
<div class="row">
<div class="col">
{{ content.sa_header}}
{{ content.sa_description}}
</div>
</div>
<div class="row">
<div class="col">
{% if content.sa_accordion_mobile['#items'].value == 1 %}
<div class="d-lg-block d-none">
{% endif %}
<div class="{{ vtabs }}">
<div class="d-lg-none d-block">
<label for="nav-tabs-mobile-js" class="visually-hidden">{{ 'Choose a Tab from the dropdown to display'|t }}</label>
<select
id="nav-tabs-mobile-js" class="nav nav-tabs nav-tabs-mobile nav-tabs-mobile-js">
{# Loops through the tab sections to print the tab section titles. #}
{% for key, item in content.sa_tab_item|filter((value, key) => key|first != '#') %}
<option value="#{{ paragraph.id.value }}-{{ key + 1 }}" class="nav-link {% if loop.first %}active{% endif %}" href="#{{ paragraph.id.value }}-{{ key + 1 }}" aria-controls="{{ paragraph.id.value }}-{{ key + 1 }}" role="tab" data-bs-toggle="tab" {% if loop.first %} aria-selected="true" {% endif %} aria-controls="{{ paragraph.id.value }}-{{ key + 1 }}" role="tab" data-bs-toggle="tab">{{ item['#paragraph'].sa_header.value }}</option>
{# <option class="nav-link {% if loop.first %}active{% endif %}" id="{{ paragraph_id }}-{{ key + 1 }}" data-bs-toggle="tab" data-bs-target="#{{ paragraph_id }}-{{ key + 1 }}" type="option" role="tab">{{ item['#paragraph'].field_title.value }}</button> #}
{% endfor %}
</select>
</div>
{% block content %}
{{ content|without('sa_header', 'sa_description', 'sa_display_as_vertical_tabs', 'sa_accordion_mobile' ) }}
{% endblock %}
</div>
{% if content.sa_accordion_mobile['#items'].value == 1 %}
</div>
{% endif %}
{% if content.sa_accordion_mobile['#items'].value == 1 %}
<div class="accordion d-lg-none d-md-block">
{% for i in content.sa_tab_item['#items']|keys %}
{{ content.sa_tab_item[i]['#paragraph']|view('accordion') }}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% if paragraph.sa_width.value is not empty %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock paragraph %}
95 changes: 95 additions & 0 deletions templates/patterns/nav_tabs/nav_tabs.ui_patterns.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
nav_tabs:
label: "Nav"
description: "The base .nav component is built with flexbox and provide a strong foundation for building all types of navigation components. It includes some style overrides (for working with lists), some link padding for larger hit areas, and basic disabled styling."
links:
- 'https://getbootstrap.com/docs/5.3/components/navs-tabs/'
category: "Navs and tabs"
variants:
default:
label: "Default"
tabs:
label: "Tabs"
tabs__fill:
label: "Tabs"
tabs__justified:
label: "Tabs"
pills:
label: "Pills"
pills__fill:
label: "Pills filled"
pills__justified:
label: "Pills filled with same width"
underline:
label: "Underline"
settings:
nav_type:
type: "select"
label: "List type"
options:
ul: "ul (Default)"
ol: "ol"
nav: "nav"
preview: "ul"
allow_expose: true
allow_token: true
fields:
items:
type: "list"
label: "Nav items"
description: "Nav items that appear inside the navigation component."
preview:
- type: "pattern"
id: "nav_item"
active: true
link:
type: 'html_tag'
tag: 'a'
value: 'Active'
attributes:
href: 'https://example.com'
- type: "pattern"
id: "dropdown"
button_type: "a"
dropdown_navbar: true
attributes:
class:
'nav-item'
title: "Dropdown"
content:
- title: "Dropdown header"
link_attributes:
class: [dropdown-header]
- title: "Action"
url: '#'
- title: "Dropdown item text"
- title: "Another action"
url: '#'
- title: "Something else here"
url: '#'
- {}
- title: "Separated link"
url: '#'
- title: "Action (button)"
link_attributes:
class: [dropdown-item]
- title: "Another action (button)"
link_attributes:
class: [dropdown-item]
- title: "Something else here (text)"
- type: "pattern"
id: "nav_item"
link:
type: 'html_tag'
tag: 'a'
value: 'Much longer nav link'
attributes:
href: 'https://example.com'
- type: "pattern"
id: "nav_item"
disabled: true
link:
type: 'html_tag'
tag: 'a'
value: 'Disabled'
attributes:
href: 'https://example.com'
10 changes: 10 additions & 0 deletions templates/patterns/nav_tabs/pattern-nav-tabs.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% if variant and variant|lower != 'default' %}
{% set variants = variant|split('__')|map(v => v|lower|replace({(v): 'nav-' ~ v})|replace({'_': '-'})) %}
{% set attributes = attributes.addClass(variants) %}
{% endif %}

{% set nav_type = nav_type|default('ul') %}

<{{ nav_type }}{{ attributes.addClass('nav').setAttribute('id', 'nav-tabs-mobile-js') }}>
{{ items }}
</{{ nav_type }}>

0 comments on commit 8ec6ced

Please sign in to comment.