Skip to content

Commit

Permalink
feat: Implement a modern footer (#400)
Browse files Browse the repository at this point in the history
* Extract StructValue class for LinkBlock

* Add footer columns

* Move social icons to footer

* Preview in Wagtail snippet editor

* Tests for the footer

* Responsive styling

* Make the footer expansion accessible

* Make tests work after rebasing onto `main` branch

* Add wagtail-factories dependency

* Improve accessibility

* Add some comments

---------

Co-authored-by: Kesara Rathnayake <[email protected]>
  • Loading branch information
mgax and kesara committed Apr 28, 2024
1 parent ec67de8 commit 0e379e7
Show file tree
Hide file tree
Showing 17 changed files with 325 additions and 54 deletions.
2 changes: 1 addition & 1 deletion ietf/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ def iab_blog_feed(monkeypatch: pytest.MonkeyPatch):
mock_get = Mock()
mock_get.return_value.text = ""
monkeypatch.setattr("ietf.home.models.get_request", mock_get)
return mock_get
return mock_get
3 changes: 2 additions & 1 deletion ietf/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ietf.home.models import HomePage, IABHomePage
from ietf.utils.models import SecondaryMenuItem, SocialMediaSettings
from ietf.utils.context_processors import get_main_menu
from ietf.utils.context_processors import get_footer, get_main_menu


def home_page(site):
Expand Down Expand Up @@ -46,4 +46,5 @@ def global_pages(request):
"MENU": lambda: get_main_menu(site),
"SECONDARY_MENU": lambda: secondary_menu(site),
"SOCIAL_MENU": lambda: social_menu(site),
"FOOTER": lambda: get_footer(),
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ $custom-spacers: (
);

$spacers: map-merge($spacers, $custom-spacers);

$enable-negative-margins: true;
2 changes: 1 addition & 1 deletion ietf/static_src/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@import 'bootstrap/scss/functions';
@import 'bootstrap/scss/variables';

@import './custom-spacers.scss';
@import './bs-configure.scss';

@import '@ietf-tools/common-bootstrap-theme/scss/ietf-theme.scss';
@import 'bootstrap/scss/bootstrap';
Expand Down
6 changes: 6 additions & 0 deletions ietf/static_src/css/utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@
.fw-semibold {
font-weight: 600 !important;
}

.u-border-lg-bottom-0 {
@include media-breakpoint-up(lg) {
border-bottom: 0 !important;
}
}
65 changes: 60 additions & 5 deletions ietf/templates/includes/footer.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,64 @@
<footer class="bg-dark text-light py-1">
<div class="container">
<ul class="nav row">
{% for item in settings.utils.FooterLinks.footer_link_items.all %}
<li class="nav-item col-6 col-lg-auto"><a class="nav-link text-light" href="{{ item.link }}">{{ item.title }}</a></li>
<div class="container my-5">
<div class="row">
{% for column in FOOTER %}
<section class="col-lg">
<div class="border-bottom u-border-lg-bottom-0 border-light border-opacity-50">
<h4 class="my-0 py-4 fs-6" role="button" aria-expanded="false">
{{ column.title }}
<i class="bi bi-chevron-down"></i>
</h4>
<ul class="list-unstyled opacity-75">
{% for link in column.links %}
{% if link.value.url and link.value.text %}
<li class="nav-item">
<a
href="{{ link.value.url }}"
class="link-underline-opacity-0 link-light fw-semibold lh-lg"
>
{{ link.value.text }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</section>
{% endfor %}
</ul>
</div>
</div>
<div class="container my-5">
<div class="d-lg-flex justify-content-between align-items-start lh-1">
<div class="d-flex fs-4 my-5 my-lg-0 ms-n2 my-5 me-3">
{% for item in SOCIAL_MENU %}
<a class="d-block text-light px-2" href="{{ item.url }}" rel="me">
<i class="bi bi-{{ item.icon }}"></i>
</a>
{% endfor %}
</div>
<ul class="
row gx-0 column-gap-5 row-gap-3 justify-content-lg-end
my-5 my-lg-0
nav opacity-75
">
{% for item in settings.utils.FooterLinks.footer_link_items.all %}
<li class="nav-item col-auto py-0">
<a href="{{ item.link }}" class="nav-link text-light fs-10 p-0">
{{ item.title }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</footer>

<script>
[... document.querySelectorAll("footer section")].forEach((section) => {
const heading = section.querySelector("h4");
heading.addEventListener("click", () => {
const expanded = section.classList.toggle("expanded");
heading.setAttribute("aria-expanded", expanded);
});
});
</script>
7 changes: 0 additions & 7 deletions ietf/templates/includes/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,6 @@
</a>
</li>
</ul>
<div class="d-flex justify-content-center ms-lg-auto me-lg-4">
{% for item in SOCIAL_MENU %}
<a class="d-block text-dark px-2" href="{{ item.url }}" rel="me">
<i class="bi bi-{{ item.icon }}"></i>
</a>
{% endfor %}
</div>
</div>
</div>
</nav>
Expand Down
2 changes: 1 addition & 1 deletion ietf/templates/includes/megamenu.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ <h6 class="mt-3 mb-1 pb-1 border-bottom">{{ section.title }}</h6>
{% for link in section.links %}
<li>
<a class="dropdown-item" href="{{ link.url }}">
{{ link.title }}
{{ link.text }}
</a>
</li>
{% endfor %}
Expand Down
30 changes: 30 additions & 0 deletions ietf/templates/includes/styles/footer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
footer section {
h4[role=button] {
@include media-breakpoint-up(lg) {
cursor: text;
}
}

h4 .bi-chevron-down {
display: block;
float: right;
@include media-breakpoint-up(lg) {
display: none;
}
}

&.expanded h4 .bi-chevron-down {
transform: rotate(180deg);
}

ul {
display: none;
@include media-breakpoint-up(lg) {
display: block;
}
}

&.expanded ul {
display: block;
}
}
1 change: 1 addition & 0 deletions ietf/templates/includes/styles/index.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import 'header.scss';
@import 'footer.scss';

.block-paragraph {
.h2, h2 {
Expand Down
27 changes: 27 additions & 0 deletions ietf/utils/blocks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.utils.functional import cached_property
from wagtail.blocks import (
CharBlock,
FloatBlock,
Expand All @@ -7,6 +8,7 @@
RichTextBlock,
StreamBlock,
StructBlock,
StructValue,
URLBlock,
)
from wagtail.contrib.table_block.blocks import TableBlock
Expand All @@ -28,11 +30,36 @@ class Meta:
template = "blocks/note_well_block.html"


class LinkStructValue(StructValue):
@cached_property
def url(self):
if external_url := self.get("external_url"):
return external_url

if page := self.get("page"):
return page.url

return ""

@cached_property
def text(self):
if title := self.get("title"):
return title

if page := self.get("page"):
return page.title

return self.get("external_url")


class LinkBlock(StructBlock):
page = PageChooserBlock(label="Page", required=False)
title = CharBlock(label="Link text", required=False)
external_url = URLBlock(label="External URL", required=False)

class Meta: # type: ignore
value_class = LinkStructValue


class MainMenuSection(StructBlock):
title = CharBlock(label="Section title", required=True)
Expand Down
54 changes: 21 additions & 33 deletions ietf/utils/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Iterable
from operator import attrgetter

from ietf.utils.models import MainMenuItem
from ietf.utils.models import FooterColumn, MainMenuItem


class MainMenu:
Expand All @@ -17,33 +17,6 @@ def get_introduction(self, page):

return ""

def get_link_url(self, link):
if external_url := link.get("external_url"):
return external_url

if page := link.get("page"):
return page.get_url(current_site=self.site)

return ""

def get_link_title(self, link):
if title := link.get("title"):
return title

if page := link.get("page"):
return page.title

return link.get("external_url")

def get_section_links(self, section):
for link in section.value.get("links"):
item = {
"title": self.get_link_title(link),
"url": self.get_link_url(link),
}
if item["title"] and item["url"]:
yield item

def get_menu_item(self, item):
main_section_links = [
{
Expand All @@ -55,7 +28,11 @@ def get_menu_item(self, item):
secondary_sections = [
{
"title": section.value.get("title"),
"links": list(self.get_section_links(section)),
"links": [
link
for link in section.value.get("links")
if link.text and link.url
],
}
for section in item.secondary_sections
]
Expand All @@ -71,10 +48,7 @@ def get_menu_item(self, item):
}

def get_menu(self):
return [
self.get_menu_item(item)
for item in self.get_items()
]
return [self.get_menu_item(item) for item in self.get_items()]


class PreviewMainMenu(MainMenu):
Expand Down Expand Up @@ -108,3 +82,17 @@ def get_main_menu(site):
return get_iab_main_menu(site)

return MainMenu(site).get_menu()


def get_footer():
return FooterColumn.objects.all()


def get_preview_footer(current):
items = [
current if item == current else item
for item in FooterColumn.objects.all()
]
if not current.pk:
items.append(current)
return sorted(items, key=attrgetter("sort_order"))
29 changes: 29 additions & 0 deletions ietf/utils/migrations/0010_footercolumn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.7 on 2024-03-29 14:06

from django.db import migrations, models
import wagtail.blocks
import wagtail.fields
import wagtail.models


class Migration(migrations.Migration):

dependencies = [
('utils', '0009_megamenu'),
]

operations = [
migrations.CreateModel(
name='FooterColumn',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('links', wagtail.fields.StreamField([('link', wagtail.blocks.StructBlock([('page', wagtail.blocks.PageChooserBlock(label='Page', required=False)), ('title', wagtail.blocks.CharBlock(label='Link text', required=False)), ('external_url', wagtail.blocks.URLBlock(label='External URL', required=False))]))], blank=True, use_json_field=True)),
('sort_order', models.PositiveSmallIntegerField()),
],
options={
'ordering': ['sort_order'],
},
bases=(wagtail.models.PreviewableMixin, models.Model),
),
]
31 changes: 30 additions & 1 deletion ietf/utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from wagtail.models import Orderable, PreviewableMixin, Site
from wagtailorderable.models import Orderable as WagtailOrderable

from ietf.utils.blocks import MainMenuSection
from ietf.utils.blocks import LinkBlock, MainMenuSection


class LinkFields(models.Model):
Expand Down Expand Up @@ -197,6 +197,35 @@ class Meta:
verbose_name_plural = "Secondary Menu"


class FooterColumn(PreviewableMixin, models.Model):
title = models.CharField(max_length=255)
links = StreamField(
[
("link", LinkBlock()),
],
blank=True,
use_json_field=True,
)
sort_order = models.PositiveSmallIntegerField()

class Meta:
ordering = ["sort_order"]

def __str__(self): # pragma: no cover
return self.title

def get_preview_template(self, request, mode_name):
return "previews/footer_column.html"

def get_preview_context(self, request, mode_name):
from .context_processors import get_preview_footer

return {
**super().get_preview_context(request, mode_name),
"FOOTER": get_preview_footer(current=self),
}


@register_setting
class SocialMediaSettings(BaseSiteSetting):
twitter_handle = models.CharField(
Expand Down
1 change: 1 addition & 0 deletions ietf/utils/templates/previews/footer_column.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% extends settings.utils.LayoutSettings.base_template %}
Loading

0 comments on commit 0e379e7

Please sign in to comment.