Skip to content

Fantastically easy overlays, dialog modals and slide-out-panes using Hotwire.

License

Notifications You must be signed in to change notification settings

martinzamuner/overlastic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation


Overlastic
Overlastic

Fantastically easy overlays using Hotwire.

Build Gem rails version turbo-rails version License

Load any page inside an overlay (dialog modal, slide-out pane, or whatever else floats your boat). As easy as replacing link_to with link_to_dialog.

Benefits

Built on top of your existing code

No need to bend your codebase in weird ways or add lots of lines that feel out of place. Just change a link whenever you want something to open as an overlay. The rest of your views, controllers and helpers stay the same.

Reuse your existing overlay views

Already have a partial for your gorgeous dialog modal? Just tell Overlastic about it inside an initializer and it will handle the rest.

Progressive enhancement

Are you a compulsive tab opener? Overlay links render as normal pages if you open them in a new tab. On top of that, everything will still work perfectly without Javascript. Overlay links will just turn into _blank links.

Installation

This gem requires a modern Rails application running turbo-rails. It supports both import map and node setups.

  1. bundle add overlastic
  2. rails overlastic:install

Usage

Most of the time you'll just need to replace a link_to with one of the overlay helpers:

<%= link_to_dialog "Open dialog", edit_article_path %>
<%= link_to_pane "Open slide-out pane", edit_article_path %>
<%= link_to_overlay "Open default overlay type", edit_article_path %>
<%= link_to_overlay "Open dialog", edit_article_path, overlay_type: :dialog %>

They work just as link_to and accept the same options. You can also pass locals to the overlay view:

<%= link_to_dialog "Open dialog", edit_article_path, overlay_args: { title: "Dialog title" } %>

Nested overlays will stack on top of each other. You can instead replace the last one or the whole stack:

<%= link_to_dialog "Open dialog", edit_article_path, overlay: :last %>
<%= link_to_dialog "Open dialog", edit_article_path, overlay: :first %>

By default, links and forms inside an overlay will drive the entire page (target _top). To keep navigation within the overlay you can set its target to _self:

<%= link_to_dialog "Open dialog", edit_article_path, overlay_target: :_self %>

To break out of an overlay with target _self you can use:

<%= link_to "Open whole page", edit_article_path, overlay: false %>

A common use case is to render a form inside an overlay. When the form is submitted, you'll validate the data and redirect to a different page if it's successful or render the form again with errors. Overlastic will handle both cases gracefully without any modifications:

if @article.save
  redirect_to article_url(@article), status: :see_other
else
  render :new, status: :unprocessable_entity
end

In case the form overlay was nested inside another overlay, you could prefer to apply the redirection to the parent overlay:

redirect_to article_url(@article), overlay: :previous, status: :see_other

Intermediate features

Adapting a view using the overlay variant

Sometimes, you may want to alter the content of a view depending on whether it's inside an overlay or not. Overlastic defines a new :overlay request variant that you can use to create custom views like new.html+overlay.erb or inside a controller like so:

respond_to do |format|
  format.turbo_stream.overlay { render :custom_view }
  format.turbo_stream.any
  format.html
end
Closing an overlay from the server

If you don't need to render any more content you can also close an overlay from the server:

if request.variant.overlay?
  close_overlay
  # close_overlay :last
  # close_overlay :all
  # close_overlay :overlay2
else
  redirect_to articles_url, status: :see_other
end
Attaching to lifecycle events

If you want to add custom behavior every time an overlay is removed or attached to the DOM, you can listen to their lifecycle events:

  • overlastic:connected
  • overlastic:disconnected

They target the first element in your view to make it easy to add listeners using libraries like Stimulus. You could, for example, have something like this:

<!-- app/views/overlay/_dialog.html.erb -->

<div data-controller="overlay" data-action="overlastic:disconnect->overlay#close">
  ...
</div>

The overlastic:disconnect event can be paused and resumed, which allows you to run lengthy functions, like animations, before being closed or replaced by another overlay:

close(event) {
  event.preventDefault()

  this.leave().then(() => {
    event.detail.resume()
  })
}

If there are many overlays being closed at the same time, all of them will be dispatched an overlastic:disconnect event. This is great to have independent animations for each of them.

Advanced features

Appending Turbo Streams to close_overlay

Sometimes, you may want not only to close an overlay, but also to deliver some other page change using a Turbo Stream:

close_overlay do
  turbo_stream.prepend("flash-messages", "Deleted!")
end
Appending Turbo Streams to every response

Overlastic can be configured to append a Turbo Stream to every response that contains an overlay. This can be very useful for automatically rendering new flash messages whenever they're available:

Overlastic.configure do |config|
  config.append_turbo_stream do
    turbo_stream.replace("flash-messages", partial: "shared/flash_messages")
  end
end

Then you'd only need to specify a flash message inside your action, when closing an overlay, or when redirecting to a different path:

def show
  flash.now[:notice] = "You've been noticed!"
end

# or

close_overlay notice: "Deleted!"

# or

redirect_to articles_path, notice: "Deleted!", status: :see_other
Rendering an overlay without an initiator

Overlastic extends the render method inside a controller to add all the same options as link_to_overlay. This allows you to force an action to render an overlay, even if it wasn't requested:

render :new, overlay: :first, overlay_target: :_self, overlay_args: { title: "New article" }
# render :edit, overlay: :last, overlay_type: :pane

Configuration

# config/initializers/overlastic.rb

Overlastic.configure do |config|
  config.overlay_types = %i[dialog pane]
  config.default_overlay = :dialog # Options: One of the defined overlay types
  config.default_action = :stack # Options: :stack, :replace_last, :replace_all
  config.default_target = :_top # Options: :_top, :_self

  # You can define a custom partial for each overlay type
  config.dialog_overlay_view_path = "overlays/dialog"
  config.pane_overlay_view_path = "overlays/pane"

  # You can append Turbo Streams to every response containing an overlay
  config.append_turbo_stream do
    turbo_stream.replace("flash-messages", partial: "shared/flash_messages")
  end
end

UI customization

Overlastic comes with default views for both the dialog and pane overlays. They are intended to provide an easy way to try the gem. For real-world usage you're expected to implement your own UI elements, or use something like Bootstrap or TailwindCSS Stimulus Components.

Default overlays

Dialog

Dialog

Pane

Dialog
Generate customizable views

Overlastic provides a generator to build your own views using the default overlays as a base. It's not advisable, though. You're better off using a UI library.

# Available options: inline, tailwind
./bin/rails generate overlastic:views --css tailwind

Development

Running the demo application
  • First you need to install dependencies with bundle && yarn && yarn build
  • Then you need to setup the DB with rails db:setup
  • Lastly you can run the demo app with rails server --port 3000
Running the tests
  • You can run the whole suite with ./bin/test test/**/*_test.rb

License

Overlastic is released under the MIT License.