app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Turboapp</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<div id='notifications'>
<%= render 'shared/flash' %>
</div>
<%= yield %>
</body>
</html>
app/views/shared/_flash.html.erb
<div id="flash">
<% flash.each do |type, message| %>
<div class="flash-overlay fixed inset-x-0 top-0 flex items-end justify-right px-4 py-5 sm:p-5 justify-end z-30 pointer-events-none">
<div
data-controller="alert"
data-alert-show-class="translate-x-0 opacity-100 bg-lemonchiffon"
data-alert-hide-class="translate-x-full opacity-0 bg-transparent"
class="flash-section max-w-sm w-full shadow-lg px-4 py-2 rounded relative bg-transparent border-l-8 border-sky-400 border-opacity-90 text-gray-600 pointer-events-auto transition translate-x-full transform ease-in-out duration-1000 opacity-0">
<div class="flash-section-one p-2">
<div class="flash-section-two flex items-start">
<div class="flash-section-three ml-3 w-0 flex-1 pt-0.5">
<p class="flash-section-four text-sm leading-5 font-medium">
<%= content_tag :div, message %>
</p>
</div>
<div class="flash-button-section ml-4 flex-shrink-0 flex">
<button data-action="alert#close" class="close-flash inline-flex text-gray-600 focus:outline-none focus:text-gray-300">
<svg class="svg-close h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<% end %>
</div>
app/assets/stylesheets/application.css
.flash-overlay {
position: fixed;
display: flex;
align-items: flex-end;
justify-content: flex-end;
left: 0px;
right: 0px;
top: 0px;
padding-left: 1rem;
padding-right: 1rem;
z-index: 30;
}
.flash-section {
position: relative;
border-left-width: 8px;
border-color: rgb(245, 158, 11);
color: rgb(75, 85, 99);
max-width: 24rem;
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
border-radius: 8px;
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.1) 0px 10px 15px -3px,
rgba(0, 0, 0, 0.1) 0px 4px 6px -4px;
}
.flash-section-one {
padding: 0.5rem;
}
.flash-section-two {
display: flex;
align-items: flex-start;
}
.flash-section-three {
flex: 1 1 0%;
margin-left: 0.75rem;
width: 0px;
padding-top: 0.125rem;
}
.flash-section-four {
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 500;
}
.flash-button-section {
display: flex;
flex-shrink: 0;
margin-left: 1rem;
}
.close-flash {
display: inline-flex;
color: rgb(75, 85, 99);
background-color: transparent;
}
.close-flash:focus {
color: rgb(209, 213, 219)
outline: 2px solid transparent;
outline-offset: 2px;
}
.svg-close {
height: 1.25rem;
width: 1.25rem;
}
.translate-x-full {
-webkit-transform: translateX(100%);
transform: translateX(100%);
}
.-translate-x-full {
-webkit-transform: translateX(-100%);
transform: translateX(-100%);
}
.translate-x-0 {
-webkit-transform: translateX(0);
transform: translateX(0);
}
.opacity-100 {
opacity: 1;
}
.opacity-0 {
opacity: 0;
}
.bg-white {
background-color: white;
}
.bg-transparent {
background-color: transparent;
}
.duration-1000 {
transition-duration: 1000ms;
}
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.pointer-events-none {
pointer-events: none;
}
.pointer-events-auto {
pointer-events: auto;
}
.bg-lemonchiffon {
background-color: lemonchiffon;
}
- [./bin/rails g stimulus alert]
app/javascript/controllers/alert_controller.js
=============================================
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static values = {
dismissAfter: Number,
showDelay: Number,
removeDelay: Number
}
static classes = ["show", "hide"]
initialize() {
this.hide()
}
connect() {
setTimeout(() => {
this.show()
}, this.showAfter)
// Auto dimiss if defined
if (this.hasDismissAfterValue || true) {
setTimeout(() => {
this.close()
// }, this.dismissAfterValue)
}, 7000)
}
}
close() {
this.hide()
setTimeout(() => {
this.element.remove()
}, this.removeAfter)
}
show() {
this.element.classList.add(...this.showClasses)
this.element.classList.remove(...this.hideClasses)
}
hide() {
this.element.classList.add(...this.hideClasses)
this.element.classList.remove(...this.showClasses)
}
get removeAfter() {
if (this.hasRemoveDelayValue) {
return this.removeDelayValue
} else {
return 5000
}
}
get showAfter() {
if (this.hasShowDelayValue) {
return this.showDelayValue
} else {
return 200
}
}
}
flash = ActionDispatch::Flash::FlashHash.new
turbo_stream.update 'service-frame', partial: "shared/flash", locals: { flash: flash }