<div id="notification-123" class="cwf-notification cwf-notification--primary " aria-expanded="false">
    <div class="cwf-notification__container ">
        <div class="cwf-notification__header">
            <div class="cwf-notification__icon">
                &#x1F40F;
            </div>
            <span class="cwf-notification__title">
                Alert
            </span>
        </div>
        <div class="cwf-notification__body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tristique quam in purus eleifend maximus. Nullam sed magna eget leo consequat faucibus in vitae magna.</div>
        <button class="cwf-notification__close" aria-label="Close notification">
            <span>Close</span>
            <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times" class="svg-inline--fa fa-times fa-w-11 cwf-notification__times" role="presentation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512">
                <path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />
            </svg>
        </button>
    </div>
</div>
{% 
    set icons = {
        'alert' : '<i class="fas fa-bullhorn"></i>',
        'info' : '<i class="far fa-sticky-note"></i>',
        'primary' : '&#x1F40F;',
        'secondary' : '&#x1F40F;',
        'success' : '<i class="far fa-check-circle"></i>'
    } 
%}
<div id="{{ id }}" class="cwf-notification cwf-notification--{{ theme }} {{ compact ? 'cwf-notification--compact' -}}" {{ dismissable ? 'aria-expanded="false"' }}>
    <div class="cwf-notification__container {{ stacked ? 'cwf-notification__container--stacked' }}">
        {% if title %}
            <div class="cwf-notification__header">
                <div class="cwf-notification__icon">
                    {{ attribute(icons, theme) }}
                </div>
                <span class="cwf-notification__title">
                    {{ title }}
                </span>
            </div>
        {% endif %}
        <div class="cwf-notification__body">{{ body }}</div>
        {% if dismissable %}
            <button class="cwf-notification__close" aria-label="Close notification">
                <span>Close</span>
                {% include '../../shared/fontawesome/times-solid.svg' with {
                    class: 'cwf-notification__times',
                    role: 'presentation'
                } %}
            </button>
        {% endif %}
    </div>
</div>
{
  "id": "notification-123",
  "title": "Alert",
  "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tristique quam in purus eleifend maximus. Nullam sed magna eget leo consequat faucibus in vitae magna.",
  "theme": "primary",
  "compact": false,
  "dismissable": true,
  "stacked": false
}
  • Content:
    // Context notification component styles
    
    @import "../../shared/scss/style";
    
    .cwf-notification {
        background: var(--cwf-notification--primary-color);
        border-bottom: 10px solid var(--cwf-notification--secondary-color);
        color: var(--cwf-notification--body-color);
        padding: .5rem 0;
        
        @include media__breakpoint {
            padding: 1rem 0;
        }
    }
    
    .cwf-notification[aria-expanded="false"] {
        display: none;
    }
    
    .cwf-notification__container {
        align-items: center;
        display: flex;
        flex-direction: column;
        flex-wrap: wrap;
        justify-content: space-between;
        margin: 0 auto;
        max-width: 1400px;
    
        @include media__breakpoint {
            flex-direction: row;
            flex-wrap: nowrap;
        }
    }
    
    // Notification themes
    .cwf-notification--alert {
        --cwf-notification--body-color: #{style__color(white)};
        --cwf-notification--primary-color: #{style__color--accent(red)};
        --cwf-notification--secondary-color: #{darken(style__color--accent(red), 10)};
        --cwf-notification--title-color: #{style__color(white)};
    }
    
    .cwf-notification--info {
        --cwf-notification--body-color: #{style__color(white)};
        --cwf-notification--primary-color: #{style__color--accent(blue)};
        --cwf-notification--secondary-color: #{darken(style__color--accent(blue), 10)};
        --cwf-notification--title-color: #{style__color(white)};
    }
    
    .cwf-notification--primary {
        --cwf-notification--body-color: #{style__color(white)};
        --cwf-notification--primary-color: #{darken(style__color(gray-dark), 6.5%)}; // #222
        --cwf-notification--secondary-color: #{style__color--primary(gray)};
        --cwf-notification--title-color: #{style__color(gold)};
    }
    
    .cwf-notification--secondary {
        --cwf-notification--body-color: #{style__color(black)};
        --cwf-notification--primary-color: #{style__color--primary(gold)};
        --cwf-notification--secondary-color: #{darken(style__color--primary(gold), 10)};
        --cwf-notification--title-color: #{style__color(black)};
    }
    
    .cwf-notification--success {
        --cwf-notification--body-color: #{style__color(white)};
        --cwf-notification--primary-color: #{style__color--accent(green)};
        --cwf-notification--secondary-color: #{darken(style__color--accent(green), 10)};
        --cwf-notification--title-color: #{style__color(white)};
    }
    
    .cwf-notification__header {
        align-items: center;
        color: var(--cwf-notification--title-color);
        display: flex;
        flex-wrap: nowrap;
        font-size: 1.25rem;
        font-weight: 700;
        justify-content: center;
        order: 1;
        padding: .5rem 1rem;
        
        @include media__breakpoint {
            align-items: flex-start;
            border-right: 1px solid var(--cwf-notification--secondary-color);
            justify-content: flex-start;
            max-width: 480px;
            margin: 0 1rem 0 0;
            order: 0;
        }
    }
    
    .cwf-notification__icon, 
    .cwf-notification__title {
        padding: 0 .5rem;
    }
    
    .cwf-notification__body {
        align-items: center;
        justify-content: center;
        order: 1;
        padding: 0 .5rem 1rem;
        text-align: center;
        
        @include media__breakpoint {
            margin-right: auto;
            order: 0;
            padding: 0 1rem;
            text-align: left;
        }
    
        > * {
            margin: 0;
            padding: 0;
        }
        
        a {
            color: var(--cwf-notification--title-color);
        }
    
        .cwf-button {
            background-color: var(--cwf-notification--primary-color) !important;
            border-color: var(--cwf-notification--secondary-color) !important;
            border-width: 2px;
            color: var(--cwf-notification--body-color) !important;
            margin: 0 1rem;
        
            &:focus, 
            &:hover {
                background-color: var(--cwf-notification--secondary-color) !important;
            }
        }
    }
    
    .cwf-notification__close {
        align-items: center;
        align-self: flex-end;
        background-color: transparent;
        border: none;
        border-radius: 4px;
        color: style__color(black);
        display: flex;
        font-size: var(--cwf-notification--font-size);
        justify-content: right;
        line-height: 1;
        margin: 0rem 1rem;
        padding: .25rem;
        color: var(--cwf-notification--body-color);
        
        @include media__breakpoint {
            align-self: flex-start;
            width: auto;
        }
    
        &:hover,
        &:focus {
            background-color: var(--cwf-notification--secondary-color);
            cursor: pointer;
        }
    }
    
    .cwf-notification__close span {
        align-content: flex-end;
    }
    
    .cwf-notification__times {
        flex: 1;
        min-width: 0.8rem;
        margin-left: 0.5rem;
        width: 0.8rem;
    }
    
    #cwf-content .cwf-notification, 
    #cwf-aside .cwf-notification {
        margin: 0 0 1rem 0;
    }
    
    // Compact variation
    .cwf-notification--compact {
        padding: .5rem 0;
        
        .cwf-notification__header {
            border: none;
            font-size: 1rem;
            margin-right: 0;
        }
    
        .cwf-notification__close {
            font-size: .9rem;
        }
    }
    
    // Stacked variation
    @include media__breakpoint {
        .cwf-notification__container--stacked {
            flex-direction: row-reverse;
            flex-wrap: wrap;
            padding: 0 1rem;
            
            .cwf-notification__header,
            .cwf-notification__body {
                border-right: none;
                max-width: none;
                padding: 0;
                width: 100%;
            }
            
            .cwf-notification__header {
                flex: 1;
            }
        
            .cwf-notification__body {
                padding-left: 3rem;
            }
    
            .cwf-notification__close {
                align-self: flex-start;
                order: -1;
            }
        }
        
        .cwf-notification--compact .cwf-notification__container--stacked .cwf-notification__body {
            padding-left: 2.6rem !important;
        }
    
    }
  • URL: /components/raw/notification/_styles.scss
  • Filesystem Path: src/components/notification/_styles.scss
  • Size: 5.7 KB
  • Content:
    import Cookies from 'js-cookie';
    
    // Provide functionality to all accordions
    class Notification {
        constructor({
            notification = 'cwf-notification',
            dismissable = 'cwf-notification__close',
        } = {}) {
            // Store all selectors
            this.selectors = {
                notification,
                dismissable
            };
    
            // Initialize arrays for the notifications...
            this.notifications = [];
            this.dismissables = [];
    
            // Bind "this" to the necessary methods
            this.onClick = this.onClick.bind(this);
        }
    
        // Get references to all accordion elements
        getReferences() {
            // Attempt to grab all notifications that are toggleable,
            this.notifications = Array.from(
                document.querySelectorAll(`.${this.selectors.notification}[aria-expanded="false"]`)
            );
    
            // Attempt to grab all dismissables,
            this.dismissables = Array.from(
                document.querySelectorAll(`.${this.selectors.dismissable}`)
            );
        }
    
        // If the user doesn't already have a dismissable cookie set, then toggle the aria-expanded attribute 
        toggleNotificationDisplay(notification) {    
            // Check if the user has already dismissed this notification
            const notificationAlreadyDismissed = this.checkExistenceOfNotificationDismissedCookie(notification);
            if (notificationAlreadyDismissed) return;
            
            // ... if not, then toggle the aria-expanded attribute
            const attribute = 'aria-expanded';
            const currentValue = notification.getAttribute(attribute) === 'true' ? true : false;
            notification.setAttribute(attribute, !currentValue);
        }
    
        // Check if the user has a cookie already set that corresponds to it's unique id
        checkExistenceOfNotificationDismissedCookie(notification) {
            return Cookies.get(notification.id) === 'false';
        }
        
        // Sets a cookie with notification's unique id as the name and a value of false. Note, cookie values are always strings.
        setNotificationDismissedCookie(notification) {
            Cookies.set(notification.id, 'false', {path: '/', sameSite: 'strict'});
        }
    
        // Handle click events
        onClick(event) {
            // Grab the current target of the event
            const { currentTarget } = event;
    
            // Get the parent notification of the clicked dismissable
            const notification = this.findParentNotification(currentTarget);
                    
            // ... and toggle the notification
            this.toggleNotificationDisplay(notification);
            
            // ... and set a cookie so we know this notification has been dismissed
            this.setNotificationDismissedCookie(notification);
            
            // Finally, prevent the default behavior...
            event.preventDefault();
        }
    
        // Find an the parent notification of a dismissable button.
        findParentNotification(dismissable) {
            return dismissable.parentNode.parentNode;
        }
    
        // Initialize the accordion panels
        initialize() {
            // First, get all references to the notifications
            this.getReferences();
            
            // If no notifications were found on the page, do nothing
            if (!this.notifications.length) return;
    
            // Toggle all aria-expanded attributes to true
            this.notifications.forEach(notification => {
                this.toggleNotificationDisplay(notification);
            });
    
            // Bind to the close button's click events if any exist
            if (!this.dismissables.length) return;
    
            return this.dismissables.forEach((toggle) =>
                toggle.addEventListener('click', this.onClick)
            );
        }
    }
    
    export default Notification;
    
  • URL: /components/raw/notification/functionality.js
  • Filesystem Path: src/components/notification/functionality.js
  • Size: 3.7 KB
  • Content:
    // Provide functionality to all notifications
    import Notification from './functionality';
    
    function initializeNotifications() {
        const notifications = new Notification();
        notifications.initialize();
    }
    
    export default initializeNotifications;
    
  • URL: /components/raw/notification/initialize.js
  • Filesystem Path: src/components/notification/initialize.js
  • Size: 249 Bytes

Notification

The notification component can be used at either the top of the page (below the VCU branding bar), the main content area or the sidebar. It should be used to draw the user’s attention to something important.

On small screens the content in the notification will be center aligned. On larger screens content will be left aligned and wrapped in a container with a max-width of 1400px.

Requirements

Body

This is the general body of content for the notification. Your basic text based WYSIWYG type of content will generally look okay in this area. It is not recommended to insert images or blockquotes into the notification.

Theme

The notification component has the ability to display with a variety of background colors depending on the theme attribute. Carefully consider the theme of the notification based on the context of the content.

The associated icon that is displayed next to the title is defined by the notification’s theme. Please only use icons defined in this framework.

Options

Notifications are configurable and provide additional options past the two required fields.

Compact

The notification component has the option to display in compact mode, which will reduce the font size and padding of the wrapping block.

Dismissable

Individual notifications have the option to be dismissable by the user. If this option is selected ensure you’ve added a specific id and the aria-expanded="false" attribute for each unique dismissable notification.

Dismissable javascript workflow

When a page that includes dismissable notifications is loaded, the aria-expanded="false" attributes on each notification are automatically toggled to aria-expanded="true" via the toggleNotificationDisplay() method, resulting in the user being able to interact with the notification content.

When a user dismisses a specific notification, a session based cookie will be set in the browser that corresponds to the notification’s unique id with a value of ‘false’ via the setNotificationDismissedCookie() method. Additionally, the notification will have it’s aria-expanded attribute toggled back to aria-expanded="false", resulting in the notification being hidden.

When the page is reloaded or a new page is navigated to, the notification javascript will again prepare to toggle all aria-expanded attributes to aria-expanded="true", but this time will only toggle notifications open that do not have a corresponding dissmissed cookie already set.

Notification cookies are good for the duration of a user’s session and only correspond to that particular domain.

Stacked

The notification has an option to stack the title and body on top of each other instead of side by side. This option may be good for notifications that require long body or title content. To stack the notification add the .cwf-notification__container--row class to the .cwf-notification__container.

Title

The title is optional in notifications. If empty, the notification body will fill the rest of the space and no icon will be displayed.