Skip to content

Commit ba5ee9d

Browse files
authored
Feature: Custom Confirmation Dialogs (#5133)
Because: - Our confirmation dialogs should have similar styling to the rest of the site. This commit: - Introduces a branded TOP-style dialog component for consistent, accessible confirmations. - Adds a Stimulus "dialog router" controller that allows dialogs to be placed anywhere in the DOM and triggered via data attributes, avoiding messy nested markup. - Integrates the new confirmation dialog for project submission deletions. <img width="966" height="579" alt="Screenshot 2025-10-17 at 09 03 20" src="https://github.com/user-attachments/assets/a2bd73e7-e42f-45d4-ab0e-9a77841933ab" />
1 parent 95edfdc commit ba5ee9d

File tree

10 files changed

+102
-10
lines changed

10 files changed

+102
-10
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<div
2+
id="<%= id %>"
3+
data-controller="overlays--dialog"
4+
data-dialog-router-target="dialog"
5+
data-action="overlays--dialog:click:outside->overlays--dialog#close turbo:submit-end->overlays--dialog#submitEnd keydown->overlays--dialog#onKeydown">
6+
<dialog
7+
data-overlays--dialog-target="dialog"
8+
class="z-50 fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 m-0 transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all data-closed:translate-y-4 data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in sm:w-full sm:max-w-lg sm:p-6 data-closed:sm:translate-y-0 data-closed:sm:scale-95 dark:bg-gray-800 dark:outline dark:-outline-offset-1 dark:outline-white/10 backdrop:bg-gray-500/75 dark:backdrop:bg-black/50">
9+
<div data-overlays--dialog-target="content">
10+
<%= content %>
11+
</div>
12+
</dialog>
13+
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Overlays::DialogComponent < ApplicationComponent
2+
def initialize(id:)
3+
@id = id
4+
end
5+
6+
private
7+
8+
attr_reader :id
9+
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Controller } from '@hotwired/stimulus'
2+
import { useClickOutside } from 'stimulus-use'
3+
4+
export default class extends Controller {
5+
static targets = ['dialog', 'content']
6+
7+
connect () {
8+
this.element.dialog = this
9+
useClickOutside(this, { element: this.contentTarget })
10+
}
11+
12+
open () {
13+
this.dialogTarget.showModal()
14+
}
15+
16+
close () {
17+
this.dialogTarget.close()
18+
}
19+
20+
submitEnd (event) {
21+
if (event.detail.success) {
22+
this.close()
23+
}
24+
}
25+
26+
onKeydown (event) {
27+
if (event.key === 'Escape') {
28+
this.close()
29+
}
30+
}
31+
}

app/components/project_submissions/user_solution_component.html.erb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,32 @@
3434
Edit
3535
<% end %>
3636

37-
<%= link_to lesson_project_submission_path(project_submission.lesson, project_submission), class: 'text-gray-700 dark:text-gray-300 group flex items-center px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-gray-200', role: 'menuitem', tabindex: '-1', data: { turbo_method: :delete, turbo_confirm: 'Are you sure? this cannot be undone.', test_id: 'delete-submission' } do %>
37+
<%= link_to(
38+
lesson_project_submission_path(project_submission.lesson, project_submission),
39+
class: 'text-gray-700 dark:text-gray-300 group flex items-center px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-gray-200',
40+
role: 'menuitem',
41+
tabindex: '-1',
42+
data: { action: 'dialog-router#open:prevent visibility#off', dialog_router_id_param: 'delete_user_submission', test_id: 'delete-submission-btn' }
43+
) do %>
3844
<%= inline_svg_tag 'icons/trash.svg', class: 'mr-3 h-4 w-4 text-gray-400 group-hover:text-gray-500 dark:group-hover:text-gray-300', aria: true, title: 'edit', desc: 'edit icon' %>
3945
Delete
4046
<% end %>
4147
</div>
4248
</div>
4349
</div>
4450
</div>
51+
52+
<%= render Overlays::DialogComponent.new(id: 'delete_user_submission') do |component| %>
53+
<div>
54+
<h3 id="dialog-title" class="text-base font-semibold text-gray-900 dark:text-white">Delete project</h3>
55+
<div class="mt-2">
56+
<p class="text-sm text-gray-600 dark:text-gray-300">Are you sure you want to remove this project? This action cannot be undone.</p>
57+
</div>
58+
</div>
59+
60+
<div class="mt-5 flex flex-row-reverse gap-x-2">
61+
<%= button_to 'Delete', lesson_project_submission_path(project_submission.lesson, project_submission), method: :delete, class: 'button button--danger bg-red-600 !px-3 !py-2 text-sm font-semibold min-w-0 focus:ring-0 focus:ring-offset-0', data: { test_id: 'confirm-delete-submission-btn'} %>
62+
<button type="button" data-action="overlays--dialog#close" class="button button--secondary px-3 py-2 text-sm font-semibold min-w-0">Cancel</button>
63+
</div>
64+
<% end %>
4565
</div>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Controller } from '@hotwired/stimulus'
2+
3+
export default class extends Controller {
4+
static targets = ['dialog']
5+
6+
open ({ params: { id } }) {
7+
const element = this.dialogTargets.find(dialog => dialog.id === id)
8+
9+
element.dialog.open()
10+
}
11+
}

app/views/layouts/admin/base.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<%= javascript_include_tag 'main', defer: true, data: { turbo_track: 'reload' } %>
2929
</head>
3030

31-
<body class="h-full bg-white text-gray-600 dark:bg-gray-900 dark:text-gray-300">
31+
<body data-controller="dialog-router" class="h-full bg-white text-gray-600 dark:bg-gray-900 dark:text-gray-300">
3232
<div>
3333
<%= render 'layouts/admin/sidebar_nav' %>
3434
<div class="lg:pl-72">

app/views/layouts/application.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<%= javascript_include_tag 'main', defer: true, data: { turbo_track: 'reload' } %>
2828
</head>
2929

30-
<body class="h-full bg-gray-50 text-gray-600 dark:bg-gray-900 dark:text-gray-300">
30+
<body data-controller="dialog-router" class="h-full bg-gray-50 text-gray-600 dark:bg-gray-900 dark:text-gray-300">
3131
<%= render AnnouncementComponent.new(announcement: Announcement.showable_messages(disabled_announcement_ids).first) %>
3232
<%= render 'shared/navbar' %>
3333
<div id="flash-messages">
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
require 'rails_helper'
2+
3+
RSpec.describe Overlays::DialogComponent, type: :component do
4+
it 'renders' do
5+
component = described_class.new(id: 'test-dialog')
6+
7+
render_inline(component)
8+
9+
expect(page).to have_css('#test-dialog')
10+
end
11+
end

spec/system/lesson_project_submissions/delete_submission_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616

1717
find(:test_id, 'submission-action-menu-btn').click
1818

19-
page.accept_confirm do
20-
find(:test_id, 'delete-submission').click
21-
end
19+
find(:test_id, 'delete-submission-btn').click
20+
find(:test_id, 'confirm-delete-submission-btn').click
2221

2322
expect(page).to have_content('Submit your solution')
2423
expect(page).to have_no_content(lesson.title)

spec/system/user_project_submissions/delete_submission_spec.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717
end
1818

1919
find(:test_id, 'submission-action-menu-btn').click
20-
21-
page.accept_confirm do
22-
find(:test_id, 'delete-submission').click
23-
end
20+
find(:test_id, 'delete-submission-btn').click
21+
find(:test_id, 'confirm-delete-submission-btn').click
2422

2523
within(:test_id, 'user-submissions-list') do
2624
expect(page).to have_no_content(lesson.title)

0 commit comments

Comments
 (0)