diff --git a/assets/app.js b/assets/app.js index 96924db..e0cf275 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,4 +1,7 @@ import './stimulus_bootstrap.js'; + +// Ahead of app.css: the .toastify--* rules in there override this stylesheet. +import 'toastify-js/src/toastify.css'; import './styles/app.css'; import htmx from 'htmx.org'; diff --git a/assets/controllers/toast_controller.js b/assets/controllers/toast_controller.js new file mode 100644 index 0000000..40b8bbe --- /dev/null +++ b/assets/controllers/toast_controller.js @@ -0,0 +1,22 @@ +import { Controller } from '@hotwired/stimulus'; +import Toastify from 'toastify-js'; + +/** + * Turns one flash message into one toast. The element itself stays empty and invisible -- + * Toastify appends its own node to the body, which is why a toast outlives the out-of-band + * swap of #flashes that spawned it. + */ +export default class extends Controller { + static values = { text: String, class: String }; + + connect() { + Toastify({ + text: this.textValue, + className: this.classValue, + duration: 5000, + gravity: 'top', + position: 'right', + close: true, + }).showToast(); + } +} diff --git a/importmap.php b/importmap.php index 85b58a2..9f90d8a 100644 --- a/importmap.php +++ b/importmap.php @@ -25,4 +25,11 @@ return [ 'htmx.org' => [ 'version' => '2.0.10', ], + 'toastify-js' => [ + 'version' => '1.12.0', + ], + 'toastify-js/src/toastify.css' => [ + 'version' => '1.12.0', + 'type' => 'css', + ], ]; diff --git a/tailwind/_components.css b/tailwind/_components.css index 67f423b..568b588 100644 --- a/tailwind/_components.css +++ b/tailwind/_components.css @@ -2,3 +2,4 @@ @import "components/button.css"; @import "components/forms.css"; @import "components/badge.css"; +@import "components/toast.css"; diff --git a/tailwind/components/toast.css b/tailwind/components/toast.css new file mode 100644 index 0000000..69ed4c7 --- /dev/null +++ b/tailwind/components/toast.css @@ -0,0 +1,21 @@ +/* Overrides for toastify-js. Its own stylesheet is vendored through the importmap and imported + in assets/app.js ahead of this file, so these rules win without extra specificity. */ +.toastify { + @apply rounded shadow-lg text-sm text-white; +} + +.toastify--success { + background: theme('colors.emerald.600'); +} + +.toastify--warning { + background: theme('colors.amber.600'); +} + +.toastify--error { + background: theme('colors.red.600'); +} + +.toastify--info { + background: theme('colors.primary'); +} diff --git a/templates/_partials/_flashes.html.twig b/templates/_partials/_flashes.html.twig index c9a7ed5..df9a08e 100644 --- a/templates/_partials/_flashes.html.twig +++ b/templates/_partials/_flashes.html.twig @@ -1,12 +1,10 @@ -{# Just the alerts. The positioned container with id="flashes" lives in layout.html.twig, because - that is the element htmx swaps out of band after a booking. Flat inline alerts for now: myep - renders these as Toastify toasts, which would mean adopting toastify-js (plan step 2/7). #} -{% set levels = { error: 'bg-red-600', warning: 'bg-amber-600', success: 'bg-emerald-600', info: 'bg-primary' } %} +{# Just the toast triggers. The container with id="flashes" lives in layout.html.twig, because + that is the element htmx swaps out of band after a booking. Each element is empty: the + toast_controller hands the message to Toastify, which renders it at the top right. #} +{% set levels = ['error', 'warning', 'success', 'info'] %} -{% for level, messages in app.flashes(levels|keys) %} +{% for level, messages in app.flashes(levels) %} {% for message in messages %} -