At the end of <body>
:
<dialog data-controller="modal-dialog" class="tw-w-[calc(100%-0.5rem)] tw-max-w-xl tw-rounded-lg">
<div class="tw-p-8">
<button
data-action="click->modal-dialog#close"
class="tw-absolute tw-top-6 tw-right-8 tw-text-5xl tw-opacity-40 hover:tw-opacity-90 tw-select-none"
>×</button>
<%= turbo_frame_tag 'modal_dialog', data: { action: 'turbo:frame-load->modal-dialog#open' } %>
</div>
</dialog>
Stimulus controller:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
connect() {
this.element.addEventListener("close", () => {
this.element.classList.remove('open')
});
}
disconnect() {
this.close();
}
open() {
this.element.showModal();
this.element.classList.add('open')
}
close() {
this.element.close();
this.element.classList.remove('open')
}
}
CSS:
// This scrolls the page to the top, which is a problem if the
// user opens the dialog not at the top of the page.
//
// The following would work but causes an horizontal layout
// shift when the scrollbar disappears:
// .no-scroll {
// overflow: hidden;
// }
//
// Keep an eye on the following thread for better solutions:
// https://github.com/whatwg/html/issues/7732
body:has(dialog[open]) {
overflow-y: scroll;
position: fixed;
width: 100%;
}
dialog {
opacity: 0;
transition: opacity 0.3s ease-out;
box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.8);
}
dialog.open {
opacity: 1;
}
dialog::backdrop {
display: none;
}
Button that opens the dialog:
<%= link_to(
fa_icon('plus', text: 'Invite user'),
new_dashboard_organization_team_member_path(@organization),
class: '!tw-py-2 !tw-px-3 !tw-text-base btn btn-default',
data: { turbo: true, turbo_frame: 'modal_dialog' }
) %>
Form that gets rendered into the dialog:
<div class="container tw-py-20">
<div class="tw-max-w-xl">
<p class="text-muted">
<span data-toggle="tooltip" title="Organization name"><%= @organization.name %></span> / <%= link_to 'Team members', dashboard_organization_team_members_path(@organization) %>
</p>
<%= turbo_frame_tag 'modal_dialog' do %>
<h1 class="tw-m-0 tw-mb-6">Invite team member</h1>
<%= render partial: 'form', locals: {
team_member_form: @team_member_form,
form_method: :post,
action_url: dashboard_organization_team_members_path,
submit_value: 'Invite'
} %>
<% end %>
</div>
</div>
Controller:
def create
team_member_form = TeamMemberForm::NewForm.new(new_team_member_params.merge(organization: organization, inviter_email: current_user.email))
if team_member_form.save
flash[:success] = 'The user has been added to the team members.'
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.action(:redirect, dashboard_organization_team_members_path(organization))
end
format.html do
redirect_to dashboard_organization_team_members_path(organization)
end
end
else
@team_member_form = team_member_form
render :new
end
end
Redirect:
// Workaround for redirecting from within a Turbo Frame:
// https://github.com/hotwired/turbo-rails/pull/367
Turbo.StreamActions.redirect = function () {
Turbo.visit(this.target);
};