[Render Preview] [Feature] Toast (sonner port, Hotwire-native)

Merged
DJ
6mo ago
+1895-1

Summary

https://github.com/user-attachments/assets/299856b7-f1b6-44e0-80dc-947176c8b8ab

Preview

https://web-1-pr-389.onrender.com/ (might not be available all the time)

Description

Hotwire-native port of shadcn/ui's sonner (originally backed by emilkowalski/sonner) implemented as a single Toast component family in RubyUI.

  • Two trigger paths. Server-pushed via Turbo Stream append into <turbo-frame id="ruby-ui-toaster">, or client-side via window.RubyUI.toast.{success,error,warning,info,loading,dismiss,promise} (registered by the Toaster Stimulus controller on connect).
  • Full sonner parity: variants (default/success/error/warning/info/loading), action + cancel buttons, auto-dismiss with hover-pause, stack with expand-on-hover, swipe-to-dismiss (pointer events), promise toasts, configurable position (6), theme/rich-colors hooks, hotkey to focus region, Escape to dismiss focused, ARIA roles (status vs alert).
  • Pure Tailwind utilities — no custom CSS file. Reuses RubyUI design tokens and tailwindcss-animate. Per-toast offsets via inline CSS variables only.
  • Rails flash bridge via turbo_frame_tag refresh: :morph (jetrockets pattern).
  • Tests follow the existing ComponentTest rendering pattern only — no new test category. 175/175 green; standardrb clean.

Files

Gem:

  • gem/lib/ruby_ui/toast/ — Region, Item, Title, Description, Icon, Action, Cancel, Close + 2 Stimulus controllers (toaster_controller.js, toast_controller.js).
  • gem/test/ruby_ui/toast{,_item,_region}_test.rb.
  • gem/lib/generators/ruby_ui/dependencies.yml — toast: entry.

Docs site:

  • Mount RubyUI::ToastRegion.new in application_layout.rb.
  • app/views/docs/toast.rb — full docs page (mount, variants, server-pushed, JS API, position, flash bridge).
  • app/controllers/docs/toast_demo_controller.rb — turbo_stream.append endpoints for live demos.
  • app/javascript/controllers/toast_demo_controller.js — Stimulus controller for JS-API buttons (Phlex blocks onclick).
  • Stimulus index: register ruby-ui--toast and ruby-ui--toaster.
  • Sidebar entry under "Toast".

Spec & plan

  • specs/2026-05-08-toast-hotwire-design.md
  • specs/2026-05-08-toast-hotwire-plan.md

(These specs are not committed; they're working docs.)

Test plan

  • cd gem && bundle exec rake — 175 runs, 749 assertions, 0 failures, 0 errors. StandardRB clean.
  • Brought up docs/.devcontainer (compose updated for monorepo layout: mounts repo at /workspaces/ruby_ui, working_dir docs, ports 3001:3000).
  • GET /docs/toast → 200, region mounted, 6 variant skeletons present.
  • POST /docs/toast_demo/success → 200, returns <turbo-stream action="append" target="ruby-ui-toaster"> with valid Toast::Item HTML.
  • Reviewer — open http://localhost:3001/docs/toast after cd docs/.devcontainer && docker compose up -d and verify visually:
    • Variants buttons spawn correct toast (icon + role).
    • Auto-dismiss after ~4s; hover pauses; leave resumes.
    • Stack of 3+ collapses; hover expands; leave collapses.
    • Swipe (drag) horizontally on a toast > 45px → dismisses; small drag → snaps back.
    • Action button click dismisses + emits ruby-ui:toast:dismiss.
    • Close X visible on hover.
    • RubyUI.toast.promise(...) from console → loading → success transition.
    • Alt+T focuses first toast; Escape dismisses focused.
    • Position prop variants (:top_right, :top_center, etc.) place region correctly.
    • Dark mode flips colors via existing tokens.

Notes for reviewer

  • The docs site mounts the Region globally; toasts are universal across the docs.
  • docs/.devcontainer/compose.yaml was rewired for the monorepo layout (was pointing at the legacy sibling web/ repo). If you use VSCode "Reopen in Container" this should still work.
  • The gem ships zero new npm dependencies.