Add a SnackBar to show HTMx errors
We do not have any design yet for errors and other notifications, so i followed material design, for now, since we already kind of use their input fields design. This time i decided to use AlpineJS because there is not that much HTML code, and the transitioning is way easier to do in AlpineJS than it would be with plain JavaScript—not to mention the bugs i would introduce.
This commit is contained in:
parent
41ce5af2ed
commit
7e8ec539ff
|
@ -702,6 +702,60 @@ tr.htmx-swapping td {
|
||||||
transition: opacity 1s ease-out;
|
transition: opacity 1s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Snackbar */
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"] {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--numerus--color--black);
|
||||||
|
color: var(--numerus--color--white);
|
||||||
|
padding: 2rem;
|
||||||
|
min-width: 28.8rem;
|
||||||
|
max-width: 56.8rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
position: fixed;
|
||||||
|
translate: -50%;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].enter, div[x-data="snackbar"] div[role="alert"].leave {
|
||||||
|
transition: transform;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].enter {
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].leave {
|
||||||
|
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].enter.start, div[x-data="snackbar"] div[role="alert"].leave.end {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].enter p {
|
||||||
|
transition: opacity;
|
||||||
|
transition-delay: 150ms;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].enter.start p {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].enter.end p {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[x-data="snackbar"] div[role="alert"].enter.end, div[x-data="snackbar"] div[role="alert"].leave.start {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Remix Icon */
|
/* Remix Icon */
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|
|
@ -426,3 +426,48 @@ htmx.on('closeModal', () => {
|
||||||
openDialog.close();
|
openDialog.close();
|
||||||
openDialog.remove();
|
openDialog.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
htmx.on(document, 'alpine:init', () => {
|
||||||
|
Alpine.data('snackbar', () => ({
|
||||||
|
show: false, toast: "", toasts: [], timeoutId: null, init() {
|
||||||
|
htmx.on('htmx:error', (error) => {
|
||||||
|
this.showError(error.detail.errorInfo.error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showError(message) {
|
||||||
|
this.toasts.push(message);
|
||||||
|
this.popUp();
|
||||||
|
},
|
||||||
|
popUp() {
|
||||||
|
if (this.toasts.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.show) {
|
||||||
|
this.dismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.toast !== "") {
|
||||||
|
// It will show after remove calls popUp again.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.toast = this.toasts[0];
|
||||||
|
this.show = true;
|
||||||
|
this.timeoutId = setTimeout(this.dismiss.bind(this), 4000);
|
||||||
|
},
|
||||||
|
dismiss() {
|
||||||
|
if (!this.show) {
|
||||||
|
// already dismissed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.show = false;
|
||||||
|
clearTimeout(this.timeoutId);
|
||||||
|
this.timeoutId = setTimeout(this.remove.bind(this), 350);
|
||||||
|
},
|
||||||
|
remove() {
|
||||||
|
clearTimeout(this.timeoutId);
|
||||||
|
this.toasts.splice(0, 1);
|
||||||
|
this.toast = "";
|
||||||
|
this.popUp();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/static/numerus.css">
|
<link rel="stylesheet" type="text/css" media="screen" href="/static/numerus.css">
|
||||||
<script src="/static/htmx@1.8.6.min.js"></script>
|
<script src="/static/htmx@1.8.6.min.js"></script>
|
||||||
<script type="module" src="/static/numerus.js"></script>
|
<script type="module" src="/static/numerus.js"></script>
|
||||||
|
<script defer src="/static/alpinejs@3.12.0.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
|
@ -53,4 +54,18 @@
|
||||||
{{- template "content" . }}
|
{{- template "content" . }}
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
<div x-data="snackbar">
|
||||||
|
<div x-show="show"
|
||||||
|
@click="dismiss"
|
||||||
|
x-cloak
|
||||||
|
x-transition:enter="enter"
|
||||||
|
x-transition:enter-start="start"
|
||||||
|
x-transition:enter-end="end"
|
||||||
|
x-transition:leave="leave"
|
||||||
|
x-transition:leave-start="start"
|
||||||
|
x-transition:leave-end="end"
|
||||||
|
role="alert">
|
||||||
|
<p x-text="toast"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue