Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions docs/app/javascript/controllers/ruby_ui/dialog_controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="dialog"
// Connects to data-controller="ruby-ui--dialog"
export default class extends Controller {
static targets = ["content"]
static targets = ["dialog"]
static values = {
open: {
type: Boolean,
Expand All @@ -11,22 +11,34 @@ export default class extends Controller {
}

connect() {
this.dialogTarget.addEventListener("close", this.handleClose)
if (this.openValue) {
this.open()
}
}

disconnect() {
this.dialogTarget.removeEventListener("close", this.handleClose)
document.body.classList.remove("overflow-hidden")
}

open(e) {
e?.preventDefault();
document.body.insertAdjacentHTML('beforeend', this.contentTarget.innerHTML)
// prevent scroll on body
document.body.classList.add('overflow-hidden')
e?.preventDefault()
this.dialogTarget.showModal()
document.body.classList.add("overflow-hidden")
}

dismiss() {
// allow scroll on body
document.body.classList.remove('overflow-hidden')
// remove the element
this.element.remove()
this.dialogTarget.close()
}

backdropClick(e) {
if (e.target === this.dialogTarget) {
this.dismiss()
}
}

handleClose = () => {
document.body.classList.remove("overflow-hidden")
}
}
2 changes: 1 addition & 1 deletion gem/lib/ruby_ui/dialog/dialog_content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def default_attrs
data_ruby_ui__dialog_target: "dialog",
data_action: "click->ruby-ui--dialog#backdropClick",
class: [
"fixed flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 backdrop:bg-background/80 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:zoom-in-95 sm:rounded-lg md:w-full",
"fixed open:flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 backdrop:bg-background/80 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:zoom-in-95 sm:rounded-lg md:w-full",
SIZES[@size]
]
}
Expand Down
15 changes: 15 additions & 0 deletions gem/test/ruby_ui/dialog_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ def test_dialog_content_has_backdrop_click_action
assert_match(/data-action="click->ruby-ui--dialog#backdropClick"/, output)
end

# Regression test: a closed native <dialog> must stay hidden. The bare `flex`
# utility (author CSS) overrides the UA `dialog:not([open]) { display: none }`,
# making the dialog always visible. Display must be gated on the open: variant.
def test_dialog_content_does_not_force_display_when_closed
output = phlex do
RubyUI.Dialog do
RubyUI.DialogContent { "Content" }
end
end

classes = output[/<dialog\b.*?\sclass="([^"]*)"/m, 1].to_s.split
refute_includes classes, "flex", "Bare `flex` forces a closed <dialog> to display; use `open:flex`"
assert_includes classes, "open:flex", "Dialog must apply flex only when open (open:flex)"
end

def test_dialog_content_sizes
{xs: "max-w-sm", sm: "max-w-md", md: "max-w-lg", lg: "max-w-2xl", xl: "max-w-4xl", full: "max-w-full"}.each do |size, expected_class|
output = phlex do
Expand Down
2 changes: 1 addition & 1 deletion mcp/data/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@
},
{
"path": "dialog_content.rb",
"content": "# frozen_string_literal: true\n\nmodule RubyUI\n class DialogContent < Base\n SIZES = {\n xs: \"max-w-sm\",\n sm: \"max-w-md\",\n md: \"max-w-lg\",\n lg: \"max-w-2xl\",\n xl: \"max-w-4xl\",\n full: \"max-w-full\"\n }\n\n def initialize(size: :md, **attrs)\n @size = size\n super(**attrs)\n end\n\n def view_template\n dialog(**attrs) do\n yield\n close_button\n end\n end\n\n private\n\n def default_attrs\n {\n data_ruby_ui__dialog_target: \"dialog\",\n data_action: \"click->ruby-ui--dialog#backdropClick\",\n class: [\n \"fixed flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 backdrop:bg-background/80 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:zoom-in-95 sm:rounded-lg md:w-full\",\n SIZES[@size]\n ]\n }\n end\n\n def close_button\n button(\n type: \"button\",\n class: \"absolute end-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none\",\n data_action: \"click->ruby-ui--dialog#dismiss\"\n ) do\n svg(\n width: \"15\",\n height: \"15\",\n viewbox: \"0 0 15 15\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\",\n class: \"h-4 w-4\"\n ) do |s|\n s.path(\n d:\n \"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\",\n fill: \"currentColor\",\n fill_rule: \"evenodd\",\n clip_rule: \"evenodd\"\n )\n end\n span(class: \"sr-only\") { \"Close\" }\n end\n end\n end\nend\n"
"content": "# frozen_string_literal: true\n\nmodule RubyUI\n class DialogContent < Base\n SIZES = {\n xs: \"max-w-sm\",\n sm: \"max-w-md\",\n md: \"max-w-lg\",\n lg: \"max-w-2xl\",\n xl: \"max-w-4xl\",\n full: \"max-w-full\"\n }\n\n def initialize(size: :md, **attrs)\n @size = size\n super(**attrs)\n end\n\n def view_template\n dialog(**attrs) do\n yield\n close_button\n end\n end\n\n private\n\n def default_attrs\n {\n data_ruby_ui__dialog_target: \"dialog\",\n data_action: \"click->ruby-ui--dialog#backdropClick\",\n class: [\n \"fixed open:flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 backdrop:bg-background/80 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:zoom-in-95 sm:rounded-lg md:w-full\",\n SIZES[@size]\n ]\n }\n end\n\n def close_button\n button(\n type: \"button\",\n class: \"absolute end-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none\",\n data_action: \"click->ruby-ui--dialog#dismiss\"\n ) do\n svg(\n width: \"15\",\n height: \"15\",\n viewbox: \"0 0 15 15\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\",\n class: \"h-4 w-4\"\n ) do |s|\n s.path(\n d:\n \"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\",\n fill: \"currentColor\",\n fill_rule: \"evenodd\",\n clip_rule: \"evenodd\"\n )\n end\n span(class: \"sr-only\") { \"Close\" }\n end\n end\n end\nend\n"
},
{
"path": "dialog_controller.js",
Expand Down