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

Site alert #105

Merged
merged 7 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions cdhweb/pages/snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from wagtail.snippets.models import register_snippet



class Level2MenuItem(Orderable, ClusterableModel):
"""
Represents a 'second-level' menu item.
Expand Down Expand Up @@ -320,3 +321,56 @@ class SocialChoices(models.TextChoices):
FieldPanel("site"),
FieldPanel("url"),
]


class AlertTypeChoices(models.TextChoices):
ALERT_WARNING = "warning", "Warning"
ALERT_INFO = "info", "Info"
ALERT_EMERGENCY = "emergency", "Emergency"


@register_snippet
class SiteAlert(models.Model):
title = models.CharField(max_length=80, blank=True)
message = RichTextField(features=["bold", "italic", "link"])
alert_type = models.CharField(
choices=AlertTypeChoices.choices, default=AlertTypeChoices.ALERT_INFO
)
display_from = models.DateTimeField(blank=True, null=True)
display_until = models.DateTimeField(blank=True, null=True)
dismissable = models.BooleanField(default=True)

panels = [
FieldPanel("title"),
FieldPanel("message"),
FieldPanel("alert_type"),
FieldPanel("display_from"),
FieldPanel("display_until"),
FieldPanel("dismissable"),
]
def __str__(self):
if self.title:
return self.title
else:
return f"Alert {self.id}"

def clean(self):
"""
Add some custom validation.
"""
errors = {}
if self.display_from and self.display_until:
# Make sure the dates are in the right order
if self.display_from > self.display_until:
inverted_date_message = "Display from date is after display until date. Are the dates in the wrong order?"
errors["display_from"] = inverted_date_message
errors["display_until"] = inverted_date_message

if errors:
raise ValidationError(errors)

return super().clean()

@property
def alert_id(self):
return f"alert_{self.id}"
15 changes: 14 additions & 1 deletion cdhweb/pages/templatetags/core_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.template.loader import render_to_string
from django.utils import timezone

from cdhweb.pages.snippets import Footer, PrimaryNavigation, SecondaryNavigation
from cdhweb.pages.snippets import Footer, PrimaryNavigation, SecondaryNavigation, SiteAlert

register = template.Library()

Expand Down Expand Up @@ -176,3 +176,16 @@ def secondary_navigation():
return data
else:
return None


@register.inclusion_tag("includes/site_alert.html", takes_context=True)
def site_alerts(context):
now = timezone.now()
site_alerts = (
SiteAlert.objects.all()
.exclude(display_from__gt=now)
.exclude(display_until__lt=now)
)
print(site_alerts)
Copy link
Member

Choose a reason for hiding this comment

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

Debugging

Copy link
Author

Choose a reason for hiding this comment

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

fixed

data = {"site_alerts": site_alerts, "request": context.get("request")}
return data
29 changes: 29 additions & 0 deletions cdhweb/static_src/components/AlertBanner/AlertBanner.mount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { InitComponent } from '../ComponentsInit';

const initComponent: InitComponent = (component) => {
const alertId = component.getAttribute('data-alert-id');
if (!alertId) {
throw Error('No banner alert ID supplied');
}
const alertHasBeenDismissed = !!localStorage.getItem(alertId);

if (alertHasBeenDismissed)
return () => {
// Alert has previously been dismissed. No need to show it. Abort.
};

component.classList.remove('u-hidden');
const dismissBtn = component.querySelector('button');

dismissBtn?.addEventListener('click', () => {
localStorage.setItem(alertId, alertId);
component.classList.add('u-hidden');
});

return () => {
// Return a cleanup function
// nothing to do in this case;
};
};

export default initComponent;
4 changes: 4 additions & 0 deletions cdhweb/static_src/components/ComponentsInit.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import MainNavMobile from './MainNavMobile/MainNavMobile.mount';
import MainNavDesktop from './MainNavDesktop/MainNavDesktop.mount';
import AlertBanner from './AlertBanner/AlertBanner.mount';

/**
* This is where the page is hydrated with types of components.
Expand Down Expand Up @@ -55,6 +56,9 @@ async function ComponentInit(
case 'main-nav-desktop':
return mountSyncComponent(MainNavDesktop);

case 'alert-banner':
return mountSyncComponent(AlertBanner);

case 'accordion':
return mountAsyncComponent(
import(
Expand Down
112 changes: 112 additions & 0 deletions cdhweb/static_src/global/components/site-alert.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
.alert-banner {
--alert-accent-color: var(--color-brand-100);
--alert-bg-color: var(--color-brand-5);

// This linear gradient has a hard stop in the middle and goes full width.
// This is so we get the left-side full bleed for the decoration area.
// The content area has a white background which hides this trickery.
background-image: linear-gradient(
to right,
var(--alert-accent-color) 50%,
var(--alert-bg-color) 50%
);

line-height: 1.5;
border-block-end: 1px solid var(--color-brand-110);
}

.alert-banner--informational,
.alert-banner--warning {
--alert-accent-color: var(--color-brand-100);
}
.alert-banner--emergency {
--alert-accent-color: var(--color-red-100);
}

// has `grid-standard`
.alert-banner__grid {
// background-color: var(--color-white);

// d = decoration (left color block and icon)
// c = content (including dismiss button)
grid-template-areas: 'd c c c c c c c c c c c';

@include lg {
grid-template-areas: 'd d c c c c c c c c c c';
}
}

.alert-banner__decoration {
--icon-accent-color: var(--icon-accent-color-dark);

grid-area: d;
background-color: var(--alert-accent-color);

// icon placement
display: grid;
justify-content: end;
padding: 6px 4px;

@include lg {
padding: 20px 8px;
}

:where(svg) {
width: 20px;
aspect-ratio: 1;
}
}

.alert-banner__content {
grid-area: c;
background-color: var(--alert-bg-color);
position: relative; // for pseudo element placement

font-size: px2rem(16);
padding-block: 8px;

@include lg {
padding-block: 16px;
}

// Visually add left padding to the alert content
&::before {
content: '';
position: absolute;
top: 0;
left: calc(-1 * var(--standard-gap));
width: var(--standard-gap);
height: 100%;
background-color: var(--alert-bg-color);
}
}

.alert-banner__heading {
font-size: px2rem(20);
}

// Wrapper for a) text and b) dismiss button
.alert-banner__content--dismissable {
display: grid;
grid-template-columns: 1fr auto;
align-items: start;
gap: 12px;
}

.alert-banner__text {
max-inline-size: px2rem(1080);
}

.alert-banner__dismiss-btn {
background: none;
border: none;
font: inherit;
cursor: pointer;

padding: 4px;

:where(svg) {
width: px2rem(18);
aspect-ratio: 1;
}
}
9 changes: 9 additions & 0 deletions cdhweb/static_src/images/sprites_src/two-tone/info.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions cdhweb/static_src/images/sprites_src/two-tone/warning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions cdhweb/static_src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
// All other components go here:
@import './global/components/main-nav-mobile.scss';
@import './global/components/main-nav-desktop.scss';
@import './global/components/site-alert.scss';
@import './global/components/skip-link.scss';
@import './global/components/home-hero.scss';
@import './global/components/standard-hero.scss';
Expand Down
2 changes: 2 additions & 0 deletions templates/includes/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
{% secondary_navigation as secondary_nav %}

<header class="header">
{% site_alerts %}

<nav class="main-nav-mobile" data-component="main-nav-mobile" aria-label="Main" data-search-url="{% url 'search' %}">
{# the following gets swapped out when react component mounts #}
<div class="mobile-menu__header">
Expand Down
40 changes: 40 additions & 0 deletions templates/includes/site_alert.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{% load wagtailcore_tags %}

{% for alert in site_alerts %}

<div role="note"
class="u-hidden alert-banner alert-banner--{{ alert.alert_type }}"
data-component="alert-banner"
data-alert-id="alert_{{ alert.alert_id }}"
aria-labelledby="alert-heading-{{ alert.alert_id|slugify }}"
>
<div class="alert-banner__grid content-width grid-standard">
<div class="alert-banner__decoration">
{% if alert.alert_type == 'warning' or alert.alert_type == 'emergency' %}
{% include 'includes/svg.html' with sprite="two-tone" svg="warning" %}
{% else %}
{% include 'includes/svg.html' with sprite="two-tone" svg="info" %}
{% endif %}
</div>
<div class="alert-banner__content
{% if alert.dismissable %}
alert-banner__content--dismissable
{% endif %}
">
<div class="alert-banner__text">
<h2 id="alert-heading-{{ alert.alert_id|slugify }}" class="alert-banner__heading">
{{ alert.get_alert_type_display }}:
{# alert.title field is optional, but title prefix (above) appears regardless #}
{{ alert.title }}
</h2>
{{ alert.message|richtext}}
</div>
{% if alert.dismissable %}
<button role="button" class="alert-banner__dismiss-btn" aria-label="Dismiss alert">
{% include 'includes/svg.html' with sprite="two-tone" svg="x" %}
</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
Loading