Skip to content

Toast component #148

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

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ report/*
.idea

# auto-gen PHP files/logs
php_errors.log
php_errors.log
166 changes: 166 additions & 0 deletions content/body/toast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<p>
<strong>Toast notifications are non-blocking alerts that provide feedback or information to users. They are hidden by default and become visible when triggered by a user action or system event.</strong> Toast notifications can be styled, positioned, and customized to suit various needs. This demo showcases an accessible implementation of toast notifications.
</p>

<h2>Shopping Site Toast Notifications Example</h2>

<div id="example2" class="enable-example">
<form id="shoppingForm">
<div class="control-group">
<p>Products:</p>
<button type="button" class="addItemButton" data-item-name="Product 1">Add Product 1 to Cart</button>
<button type="button" class="addItemButton" data-item-name="Product 2">Add Product 2 to Cart</button>
<button type="button" class="addItemButton" data-item-name="Product 3">Add Product 3 to Cart</button>
</div>
<div class="control-group">
<label for="discountCode">Discount Code:</label>
<input type="text" id="discountCode" placeholder="Enter discount code">
</div>
<div class="button-group">
<button type="button" id="applyDiscountButton">Apply Discount</button>
<button type="button" id="checkoutButton">Checkout</button>
</div>
</form>
</div>

<?php includeShowcode("example2"); ?>

<script type="application/json" id="example2-props">
{
"replaceHtmlRules": {},
"steps": [
{
"label": "Create markup",
"highlight": "",
"notes": ""
},
{
"label": "Create JavaScript events for toast script",
"highlight": "%JS% toastModule.create; toastModule.init",
"notes": "When the page is loaded, create the toast DOM object and initialize the events that will display the toasts."
},
{
"label": "Create the show and hide methods for the toast",
"highlight": "%JS% toastModule.show; toastModule.hide",
"notes": "Ensure the toasts are shown and hidden appropriately with ARIA attributes."
},
{
"label": "Set up the CSS",
"highlight": "%CSS%toast-css~ .toast; .toast::before; .toast--hidden ||| border[^:]*: 1px solid transparent; ",
"notes": "The styling for the toast notifications ensures they are visible and accessible."
}
]
}
</script>

<h2>Accessible Toast Notifications Example</h2>

<?php includeStats([
"isForNewBuilds" => true,
"comment" => "Recommended for new and existing work.",
]); ?>
<?php includeStats(["isNPM" => true]); ?>

<p>
In order to make toast notifications accessible, there are a few considerations:
</p>
<ol>
<li>Toasts should be announced by screen readers when they appear.</li>
<li>Keyboard users should be able to navigate to and dismiss toasts.</li>
<li>Toasts should support different levels of severity (normal, error, warning, success), each with a unique color for visual distinction.</li>
</ol>
<p>
Our implementation ensures that toasts are fully accessible and follow best practices for ARIA and keyboard interaction.
When a toast appears, it is announced to screen readers. Toasts will remain visible until manually dismissed by the user.
Additionally, all toasts are stored in a toast rack for future reference.
</p>

<div id="example1" class="enable-example">
<div class="controls">
<div class="control-group">
<label for="messageInput">Toast Message:</label>
<input type="text" id="messageInput" placeholder="Toast Message" value="A new toast message!">
</div>
<div class="control-group">
<label>Position:</label>
<div>
<label><input type="radio" name="position" value="bottom-right" checked> Bottom Right</label>
<label><input type="radio" name="position" value="top-right"> Top Right</label>
<label><input type="radio" name="position" value="top-left"> Top Left</label>
<label><input type="radio" name="position" value="bottom-left"> Bottom Left</label>
<label><input type="radio" name="position" value="top-center"> Top Center</label>
<label><input type="radio" name="position" value="bottom-center"> Bottom Center</label>
</div>
</div>
<div class="control-group">
<label>Level:</label>
<div>
<label><input type="radio" name="level" value="normal" checked> Normal</label>
<label><input type="radio" name="level" value="error"> Error</label>
<label><input type="radio" name="level" value="warning"> Warning</label>
<label><input type="radio" name="level" value="success"> Success</label>
</div>
</div>
<div class="control-group">
<label>Aria Live:</label>
<div>
<label><input type="radio" name="ariaLive" value="polite" checked> Polite</label>
<label><input type="radio" name="ariaLive" value="assertive"> Assertive</label>
</div>
</div>
<div class="control-group">
<label for="maxVisibleInput">Max Visible:</label>
<input type="number" id="maxVisibleInput" placeholder="Max Visible" value="2">
</div>
<div class="button-group">
<button id="showToastButton">Show Toast</button>
<button id="toggleRackButton">Toggle Toast Rack</button>
</div>
</div>
<div id="toastRack" class="toast-rack">
<button id="clearAllButton">Clear All</button>
<div id="rackContent"></div>
<div id="rackContentStatus"></div>
</div>
<div id="status" class="status"></div>
</div>

<script src="toast.js"></script>
<script src="app.js"></script>

<script>
document.addEventListener('DOMContentLoaded', function () {
const addItemButtons = document.querySelectorAll('.addItemButton');
const applyDiscountButton = document.getElementById('applyDiscountButton');
const checkoutButton = document.getElementById('checkoutButton');

addItemButtons.forEach(button => {
button.addEventListener('click', function () {
const itemName = this.getAttribute('data-item-name');
if (itemName) {
console.log(`Adding item: ${itemName}`);
app.toast.showToast(`You have added ${itemName} to your cart.`, 'normal');
} else {
console.warn('Item name not found');
app.toast.showToast('Please select an item.', 'warning');
}
});
});

applyDiscountButton.addEventListener('click', function () {
const discountCode = document.getElementById('discountCode').value;
if (discountCode) {
console.log(`Applying discount code: ${discountCode}`);
app.toast.showToast('Discount code applied successfully. You saved 20% on your order.', 'success');
} else {
console.warn('Discount code not entered');
app.toast.showToast('Please enter a discount code.', 'warning');
}
});

checkoutButton.addEventListener('click', function () {
console.log('Checkout initiated');
app.toast.showToast('Your order has been placed successfully. Order number: 123456.', 'success');
});
});
</script>
7 changes: 7 additions & 0 deletions content/bottom/toast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

<script type="module">
import toastModule from "/js/modules/toast.js"
import app from "/js/demos/toast.js"

app.init();
</script>
2 changes: 2 additions & 0 deletions content/head/toast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<link rel="stylesheet" type="text/css" href="css/toast.css" >
<link rel="stylesheet" type="text/css" href="css/toast-demo.css" >
59 changes: 59 additions & 0 deletions css/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin: 20px;
}

.controls {
margin-bottom: 20px;
display: flex;
flex-direction: column;
gap: 10px;
}

.control-group {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 10px;
}

.control-group label {
font-weight: normal;
}

.control-group div {
display: flex;
gap: 10px;
}

.button-group {
display: flex;
gap: 10px;
}

.toast-rack {
position: fixed;
top: 20px;
right: 20px;
background: rgba(241, 241, 241, 0.9);
border: 1px solid #ddd;
padding: 10px;
width: 300px;
max-height: 400px;
overflow-y: auto;
display: none;
z-index: 9998;
}

.toast-rack button {
display: block;
margin-bottom: 10px;
}

.status {
margin-top: 20px;
font-size: 16px;
}
18 changes: 18 additions & 0 deletions css/toast-demo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.enable-toast__controls {
margin-bottom: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.enable-toast__controls__control-group {
display: flex;
flex-direction: column;
margin-bottom: 0.625rem;
}
.enable-toast__controls__label {
font-weight: normal;
}
.enable-toast__controls__button-group {
display: flex;
gap: 0.625rem;
}
76 changes: 76 additions & 0 deletions css/toast.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.enable-toast__container {
position: fixed;
z-index: 9999;
display: flex;
transition: all 0.5s ease-in-out;
}
.enable-toast__container--bottom-right,
.enable-toast__container--bottom-left,
.enable-toast__container--bottom-center {
bottom: 20px;
}
.enable-toast__container--top-right,
.enable-toast__container--top-left,
.enable-toast__container--top-center {
top: 20px;
}
.enable-toast__container--bottom-right,
.enable-toast__container--top-right {
right: 20px;
}
.enable-toast__container--bottom-left,
.enable-toast__container--top-left {
left: 20px;
}
.enable-toast__container--top-center,
.enable-toast__container--bottom-center,
.enable-toast__container--middle-center {
left: 50%;
transform: translateX(-50%);
}
.enable-toast__container--middle-center {
top: 50%;
transform: translate(-50%, -50%);
}
.enable-toast__toast {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 0;
background: #333;
color: #fff;
padding: 10px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.enable-toast__toast--visible {
opacity: 1;
}
.enable-toast__toast--exit {
opacity: 0;
}
.enable-toast__close-button {
background: none;
border: none;
color: #fff;
font-size: 16px;
cursor: pointer;
}
.enable-toast__close-button:hover {
color: #ff0000;
}
.enable-toast__rack {
position: fixed;
top: 20px;
right: 20px;
background: rgba(241, 241, 241, 0.9);
border: 1px solid #ddd;
padding: 10px;
width: 300px;
max-height: 400px;
overflow-y: auto;
display: none;
z-index: 9998;
}
Loading
Loading