Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement a modern footer #400

Merged
merged 12 commits into from
Apr 28, 2024
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,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] Should we be allowing a column with no links?

[Minor] Also, if there's a maximum number of columns supported by the design, we should mention it here as help text, and add a max_num field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'm not a fan of making body content mandatory. No content editor will create an empty column and it doesn't really break anything. It just makes testing more annoying. Also, each column is a different snippet, so there's no good way to enforce a limit. But again, the content editor can make a judgement call for the number of columns, taking into account the length of the titles and link texts, and using the preview to see what it looks like.

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