Skip to content

Commit

Permalink
Added SnackBar component
Browse files Browse the repository at this point in the history
  • Loading branch information
SweetDealer committed Jan 10, 2024
1 parent 7e696a2 commit d442034
Show file tree
Hide file tree
Showing 9 changed files with 421 additions and 17 deletions.
12 changes: 10 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@elsci-io/ui-essential",
"version": "1.0.34",
"version": "1.0.35",
"description": "Material Design components created for products built by elsci.io",
"main": "src/index.js",
"type": "module",
Expand All @@ -21,13 +21,15 @@
"exports": {
".": "./src/index.js",
"./styles": "./src/style.scss",
"./bundle.css": "./dist/bundle.css",
"./bundle.js": "./dist/bundle.js"
"./bundle.css": "./dist/styles.bundle.css",
"./bundle.js": "./dist/index.bundle.js"
},
"devDependencies": {
"chai": "4.3.7",
"css-loader": "6.7.3",
"html-webpack-plugin": "5.5.3",
"ignore-emit-webpack-plugin": "^2.0.6",
"jsdom": "22.1.0",
"mini-css-extract-plugin": "2.7.5",
"mocha": "10.2.0",
"sass": "1.62.1",
Expand All @@ -38,7 +40,6 @@
"webpack": "5.82.1",
"webpack-cli": "5.1.1",
"webpack-dev-server": "4.15.1",
"jsdom": "22.1.0",
"xhr2": "0.2.1"
}
}
126 changes: 126 additions & 0 deletions src/SnackBar/SnackBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {safeText} from "../utils.js";

export default class SnackBar {
/**
* SnackBar is a tiny notification displayed at the bottom of the screen. It's allowed to have only one SnackBar in
* the same time. Creating a new SnackBar will remove the old one. Default living time is 15 sec after which
* SnackBar disappears.
* Properties:
* - msgText - main text (e.g. Smth was deleted)
* - btnText - OK button text (e.g. Undo)
* - btnCb - function to be called when OK btn pressed
* - ttl - aka TimeToLive in milliseconds
*/
/** @param {string} - needed in order to be able to create multiple snackbars on a page */
#snackBarId;
/** @param {number} - needed in order to delete the previous timeout when a new one appears */
#timeoutId;
/** @param {HTMLElement} */
#snackBarEl;
#onButtonClickCb = ()=>{}

/** @param {string} id */
constructor(id = 'snackBar') {
this.#snackBarId = id;
}

/**
* @param {{msgText:string, btnText:string, btnCb:function, ttl:number}} prop
*/
show(prop) {
SnackBar.#validateProperties(prop);
this.#onButtonClickCb = prop.btnCb;
this.#removeExistingIfNeeded();
this.#snackBarEl = this.#createSnackBarElement(this.#snackBarId, prop);
this.#setEventListeners();
this.#timeoutId = setTimeout(() => {
this.#removeEl()
}, prop.ttl || 15 * 1000);
}

#getOkButton() {
return this.#snackBarEl.querySelector('[js-ok]');
}

#getCloseButton() {
return this.#snackBarEl.querySelector('[js-close]');
}

#getSnackbarEl() {
return document.getElementById(`${this.#snackBarId}`);
}

#removeExistingIfNeeded(){
const existing = this.#getSnackbarEl();
if (existing)
existing.remove();
if (this.#timeoutId)
window.clearTimeout(this.#timeoutId)
}

#setEventListeners() {
const okButton = this.#getOkButton();
if (okButton) {
okButton.addEventListener('click', () => {
this.#removeEl();
this.#onButtonClickCb();
})
}
const closeButton = this.#getCloseButton();
closeButton.addEventListener('click', () => {this.#removeEl()})
}

#removeEl() {
this.#snackBarEl.remove();
const containerEl = SnackBar.#getSnackBarContainerEl();
if (containerEl.children.length === 0)
containerEl.remove();
}

/**
* @param {string} id
* @param {{msgText:string, btnText:string, btnCb:function, ttl:number}} prop
* @return {HTMLDivElement}
*/
#createSnackBarElement(id, prop) {
const containerEl = SnackBar.#getSnackBarContainerEl();
containerEl.insertAdjacentHTML("beforeend", SnackBar.#htmlTemplate(prop.msgText, prop.btnText, this.#snackBarId));
return containerEl.lastElementChild;
}

/**
* @param {string} messageText
* @param {string?} buttonText
* @param {string} elementId
* @return {string}
*/
static #htmlTemplate(messageText, buttonText, elementId) {
return `
<div id="${safeText(elementId)}" class="snackbar">
<div class="snackbar__label">${safeText(messageText)}</div>
<div class="snackbar__buttons">
${buttonText ? `<button class="snackbar__button-ok" js-ok>${safeText(buttonText)}</button>` : ''}
<button class="snackbar__button-close material-symbols-outlined" js-close title="Close">close</button>
</div>
</div>`
}

/** @param {{msgText:string, btnText:string, btnCb:function, ttl:number}} prop */
static #validateProperties(prop) {
if (!prop) throw new Error('No SnackBar properties');
if (!prop.msgText) throw new Error('Empty SnackBar message text');
if (prop.btnText && !prop.btnCb) throw new Error('No callback for SnackBar button');
if (!prop.btnText && prop.btnCb) throw new Error('No SnackBar button text');
if (prop.btnCb && (typeof prop.btnCb !== "function")) throw new Error('Callback for SnackBar button is not a function');
if (prop.ttl && (typeof prop.ttl !== "number")) throw new Error('TTL is not a number');
}

static #getSnackBarContainerEl(){
let container = document.body.querySelector(".snackbar-container");
if (!container){
document.body.insertAdjacentHTML("beforeend", "<div class='snackbar-container'/>");
container = document.body.lastElementChild;
}
return container;
}
}
94 changes: 94 additions & 0 deletions src/SnackBar/SnackBar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@

.snackbar-container {
--snackbar-background-color: rgb(51, 51, 51);
--snackbar-background-color-hover: rgb(84, 84, 84);
--snackbar-text-label-color: rgba(255, 255, 255, 0.87);
--snackbar-text-action-color: rgb(187, 134, 252);
--snackbar-text-action-disabled-color: rgb(140, 140, 140);
z-index : 8;
margin : 0.5rem;
position : fixed;
right : 0;
bottom : 0;
left : 0;
display : flex;
align-items : start;
justify-content : center;
box-sizing : border-box;
flex-direction: column;
}

.snackbar {
margin: 0.5rem;
background-color : var(--snackbar-background-color);
min-width : 344px;
box-shadow : var(--box-shadow);
max-width : 672px;
border-radius : 4px;
display : flex;
align-items : center;
justify-content : flex-start;
}

@media (max-width : 480px), (max-width : 344px){
.snackbar__surface{
min-width : 100%;
}
}

.snackbar__label{
color : var(--snackbar-text-label-color);
}

.snackbar__label{
font-size : 0.875rem;
line-height : 1.25rem;
font-weight : 400;
letter-spacing : 0.012rem;
flex-grow : 1;
box-sizing : border-box;
margin : 0;
padding : 1rem;
}


.snackbar__buttons {
margin-left : 0.5rem;
margin-right : 0.5rem;
display : flex;
flex-shrink : 0;
align-items : center;
box-sizing : border-box;
}

.snackbar__button-ok {
background: var(--snackbar-background-color);
color : var(--snackbar-text-action-color);
border: none;
cursor: pointer;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
margin-right: 0.5rem;
}

.snackbar__button-ok:disabled, .snackbar__button-close:disabled {
border: none;
color: var(--snackbar-text-action-disabled-color);
pointer-events: none;
}

.snackbar__button-ok:hover, .snackbar__button-close:hover{
background: var(--snackbar-background-color-hover);
}

.snackbar__button-close {
background: var(--snackbar-background-color);
color : var(--snackbar-text-label-color);
border: none;
cursor: pointer;
font-size: 1rem;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
padding: 0;
}
Loading

0 comments on commit d442034

Please sign in to comment.