Skip to content

Instantly share code, notes, and snippets.

@cvkmohan
Created July 27, 2021 04:11
Show Gist options
  • Save cvkmohan/33056905768ec7834e772b72c738aa80 to your computer and use it in GitHub Desktop.
Save cvkmohan/33056905768ec7834e772b72c738aa80 to your computer and use it in GitHub Desktop.
Rails Flash Messages using Tailwind, Stimulus and ViewComponents
<% flash.each do |type, title| %>
<%= render FlashComponent.new(type: type, title: title) %>
<% end %>
@keyframes flash-countdown {
from {
width: 100%;
}
to {
width: 0;
}
}
class FlashComponent < ViewComponent::Base
def initialize(type:, title:)
super
@type = type
@title = title
@icon = icon.html_safe
@icon_color_class = icon_color_class
@icon_bg_class = icon_bg_class
@timeout ||= timeout
end
def icon
case @type
when 'notice'
'<path d="M20 3.33331C10.8 3.33331 3.33337 10.8 3.33337 20C3.33337 29.2 10.8 36.6666 20 36.6666C29.2 36.6666
36.6667 29.2 36.6667 20C36.6667 10.8 29.2 3.33331 20 3.33331ZM16.6667 28.3333L8.33337 20L10.6834 17.65L16.6667
23.6166L29.3167 10.9666L31.6667 13.3333L16.6667 28.3333Z"/>'
when 'error'
'<path d="M20 3.36667C10.8167 3.36667 3.3667 10.8167 3.3667 20C3.3667 29.1833 10.8167 36.6333 20 36.6333C29.1834
36.6333 36.6334 29.1833 36.6334 20C36.6334 10.8167 29.1834 3.36667 20 3.36667ZM19.1334
33.3333V22.9H13.3334L21.6667 6.66667V17.1H27.25L19.1334 33.3333Z"/>'
else
'<path d="M20 3.33331C10.8 3.33331 3.33337 10.8 3.33337 20C3.33337 29.2 10.8 36.6666 20 36.6666C29.2 36.6666
36.6667 29.2 36.6667 20C36.6667 10.8 29.2 3.33331 20 3.33331ZM21.6667 28.3333H18.3334V25H21.6667V28.3333ZM21.6667
21.6666H18.3334V11.6666H21.6667V21.6666Z"/>'
end
end
def timeout
case @type
when 'notice'
8
else
15
end
end
def icon_color_class
case @type
when 'notice'
'text-green-500 dark:text-green-400'
when 'error'
'text-red-500 dark:text-red-400'
else
'text-yellow-400 dark:text-yellow-300'
end
end
def icon_bg_class
case @type
when ('notice' || 'success')
'bg-green-500'
when 'error'
'bg-red-500'
else
'bg-yellow-400'
end
end
end
<div class="flex w-full max-w-sm mx-auto overflow-hidden bg-white rounded-lg shadow-md dark:bg-gray-800 pointer-events-auto hidden fixed top-10 lg:right-10 z-50 border mt-2" data-flash-timeout="<%= @timeout %>" data-controller="flash">
<div class="flex items-center justify-center w-12 <%= @icon_bg_class %>">
<svg class="w-6 h-6 text-white fill-current" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<%= @icon %>
</svg>
</div>
<div class="px-4 py-2 -mx-3">
<div class="mx-3">
<span class="font-semibold capitalize <%= @icon_color_class %>"> <%= @type %> </span>
<p class="text-sm text-gray-600 dark:text-gray-200"> <%= @title %> </p>
</div>
<div class="<%= @icon_bg_class %> rounded-lg h-1 w-0" data-flash-target="countdown"></div>
</div>
<div class="ml-auto flex-shrink-0 flex">
<button class="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150" data-action="flash#close" type="button">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
import { Controller } from 'stimulus'
export default class extends Controller {
static targets = ['countdown'];
connect () {
const timeoutSeconds = parseInt(this.data.get('timeout'))
if (!this.isPreview) {
setTimeout(() => {
this.element.classList.remove('hidden')
this.element.classList.add(
'transform',
'ease-out',
'duration-300',
'transition',
'translate-y-2',
'opacity-0',
'sm:translate-y-0',
'sm:translate-x-2'
)
setTimeout(() => {
this.element.classList.add(
'translate-y-0',
'opacity-100',
'sm:translate-x-0'
)
}, 100)
if (this.hasCountdownTarget) {
this.countdownTarget.style.animation =
'flash-countdown linear ' + timeoutSeconds + 's'
}
}, 500)
this.timeoutId = setTimeout(() => {
this.close()
}, timeoutSeconds * 1000 + 500)
}
}
stop () {
clearTimeout(this.timeoutId)
this.timeoutId = null
}
close () {
this.element.classList.remove(
'transform',
'ease-out',
'duration-300',
'translate-y-2',
'opacity-0',
'sm:translate-y-0',
'sm:translate-x-2',
'translate-y-0',
'sm:translate-x-0'
)
this.element.classList.add('ease-in', 'duration-100')
setTimeout(() => {
this.element.classList.add('opacity-0')
}, 100)
setTimeout(() => {
this.element.remove()
}, 300)
}
get isPreview () {
return document.documentElement.hasAttribute('data-turbolinks-preview')
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment