diff --git a/docs/app/javascript/controllers/index.js b/docs/app/javascript/controllers/index.js index e68815bd..60911b67 100644 --- a/docs/app/javascript/controllers/index.js +++ b/docs/app/javascript/controllers/index.js @@ -13,6 +13,9 @@ application.register("ruby-ui--accordion", RubyUi__AccordionController) import RubyUi__AlertDialogController from "./ruby_ui/alert_dialog_controller" application.register("ruby-ui--alert-dialog", RubyUi__AlertDialogController) +import RubyUi__AvatarController from "./ruby_ui/avatar_controller" +application.register("ruby-ui--avatar", RubyUi__AvatarController) + import RubyUi__CalendarController from "./ruby_ui/calendar_controller" application.register("ruby-ui--calendar", RubyUi__CalendarController) diff --git a/docs/app/javascript/controllers/ruby_ui/avatar_controller.js b/docs/app/javascript/controllers/ruby_ui/avatar_controller.js new file mode 100644 index 00000000..3643f26a --- /dev/null +++ b/docs/app/javascript/controllers/ruby_ui/avatar_controller.js @@ -0,0 +1,29 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ["image", "fallback"]; + + connect() { + if (!this.hasImageTarget) return; + + if (this.imageTarget.complete && this.imageTarget.naturalWidth > 0) { + this.showImage(); + } else { + this.showFallback(); + } + } + + showImage() { + this.imageTargets.forEach((image) => image.classList.remove("hidden")); + this.fallbackTargets.forEach((fallback) => + fallback.classList.add("hidden"), + ); + } + + showFallback() { + this.imageTargets.forEach((image) => image.classList.add("hidden")); + this.fallbackTargets.forEach((fallback) => + fallback.classList.remove("hidden"), + ); + } +} diff --git a/gem/lib/ruby_ui/avatar/avatar.rb b/gem/lib/ruby_ui/avatar/avatar.rb index 01b96781..314ae582 100644 --- a/gem/lib/ruby_ui/avatar/avatar.rb +++ b/gem/lib/ruby_ui/avatar/avatar.rb @@ -24,6 +24,9 @@ def view_template(&) def default_attrs { + data: { + controller: "ruby-ui--avatar" + }, class: ["relative flex shrink-0 overflow-hidden rounded-full", @size_classes] } end diff --git a/gem/lib/ruby_ui/avatar/avatar_controller.js b/gem/lib/ruby_ui/avatar/avatar_controller.js new file mode 100644 index 00000000..3643f26a --- /dev/null +++ b/gem/lib/ruby_ui/avatar/avatar_controller.js @@ -0,0 +1,29 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ["image", "fallback"]; + + connect() { + if (!this.hasImageTarget) return; + + if (this.imageTarget.complete && this.imageTarget.naturalWidth > 0) { + this.showImage(); + } else { + this.showFallback(); + } + } + + showImage() { + this.imageTargets.forEach((image) => image.classList.remove("hidden")); + this.fallbackTargets.forEach((fallback) => + fallback.classList.add("hidden"), + ); + } + + showFallback() { + this.imageTargets.forEach((image) => image.classList.add("hidden")); + this.fallbackTargets.forEach((fallback) => + fallback.classList.remove("hidden"), + ); + } +} diff --git a/gem/lib/ruby_ui/avatar/avatar_fallback.rb b/gem/lib/ruby_ui/avatar/avatar_fallback.rb index a1a5a538..29c1b6b6 100644 --- a/gem/lib/ruby_ui/avatar/avatar_fallback.rb +++ b/gem/lib/ruby_ui/avatar/avatar_fallback.rb @@ -10,6 +10,9 @@ def view_template(&) def default_attrs { + data: { + ruby_ui__avatar_target: "fallback" + }, class: "flex h-full w-full items-center justify-center rounded-full bg-muted" } end diff --git a/gem/lib/ruby_ui/avatar/avatar_image.rb b/gem/lib/ruby_ui/avatar/avatar_image.rb index bce164e3..aede5918 100644 --- a/gem/lib/ruby_ui/avatar/avatar_image.rb +++ b/gem/lib/ruby_ui/avatar/avatar_image.rb @@ -17,7 +17,11 @@ def view_template def default_attrs { loading: "lazy", - class: "aspect-square h-full w-full", + data: { + ruby_ui__avatar_target: "image", + action: "load->ruby-ui--avatar#showImage error->ruby-ui--avatar#showFallback" + }, + class: "hidden aspect-square h-full w-full", alt: @alt, src: @src } diff --git a/gem/test/ruby_ui/avatar_test.rb b/gem/test/ruby_ui/avatar_test.rb index e4b8e550..317b80b9 100644 --- a/gem/test/ruby_ui/avatar_test.rb +++ b/gem/test/ruby_ui/avatar_test.rb @@ -12,5 +12,10 @@ def test_render_with_all_items end assert_match(/joeldrapper/, output) + assert_match(/data-controller="ruby-ui--avatar"/, output) + assert_match(/data-ruby-ui--avatar-target="image"/, output) + assert_match(/load->ruby-ui--avatar#showImage error->ruby-ui--avatar#showFallback/, output) + assert_match(/data-ruby-ui--avatar-target="fallback"/, output) + assert_match(/hidden aspect-square/, output) end end