diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b201c..a424a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. Note that the versions prior to `0.6.0` are very unstable. +## Unreleased + +### Changed + +- Default toast component styles now support Tailwind 3 and 4, and docs are changed accordingly. + +### Fixed + +- Connection notices can be shown when Tailwind CSS 4 gives the `hidden` attribute precedence over display styles. + ## [v0.10.2] (2026-09-04) ### Fixed diff --git a/README.md b/README.md index e1fff7a..7785c96 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Live Toast is a drop-in replacement for the flash system in Phoenix/LiveView. - **⚙️ Highly configurable:** Looks good out of the box, but can be changed in pretty much any way you want. - **🌍 Simple asset delivery:** `LiveToast` simply ships Tailwind classes and lets your project bundle them up. No CSS drop-in required. +- **🧩 Tailwind compatibility:** The default toast styles work with Tailwind CSS 3.4 and 4.x. ## Installation @@ -66,8 +67,24 @@ let liveSocket = new LiveSocket('/live', Socket, { }) ``` -Then, add `'../deps/live_toast/lib/**/*.*ex'` to your list of paths Tailwind will look for class names, in your -`tailwind.config.js`: +LiveToast's classes need to be included in Tailwind's source detection. The configuration is different for Tailwind 4 +and Tailwind 3. + +### Tailwind 4 + +Add LiveToast as an explicit source in `assets/css/app.css`: + +```css +/* Keep your existing Tailwind import and source declarations. */ +@source "../../deps/live_toast/lib"; +``` + +The source path is relative to the stylesheet. For an umbrella application, it will usually be +`"../../../../deps/live_toast/lib"`. + +### Tailwind 3 + +Add `'../deps/live_toast/lib/**/*.*ex'` to the `content` paths in `tailwind.config.js`: ```javascript // assets/tailwind.config.js @@ -82,12 +99,17 @@ module.exports = { } ``` -Your particular file will look different but all you need to do is make sure the last line is there. +Your particular file will look different, but all you need to do is make sure the last line is there. > **Note for Umbrella Apps:** > If you're using an umbrella application, your paths above may look different. You'll probably have an extra folder in > there, so the line you need to add would be more like `"../../../deps/live_toast/lib/**/*.*ex"` +LiveToast's default class functions intentionally use syntax supported by both Tailwind 3.4 and 4.x. If you provide a +custom `toast_class_fn` or `group_class_fn`, those classes are compiled by your application and must be compatible with +the Tailwind version it uses. Tailwind's [upgrade guide](https://tailwindcss.com/docs/upgrade-guide) covers changes to +border and ring defaults, important modifiers, arbitrary variants, and other utilities. + Finally, replace your `<.flash_group />` component with the new ``. It's most likely in your `app.html.heex`: @@ -424,14 +446,14 @@ defmodule MyModule do def toast_class_fn(assigns) do [ # base classes - "group/toast z-100 pointer-events-auto relative w-full items-center justify-between origin-center overflow-hidden rounded-lg p-4 shadow-lg border col-start-1 col-end-1 row-start-1 row-end-2", + "group/toast z-[100] pointer-events-auto relative w-full items-center justify-between origin-center overflow-hidden rounded-lg p-4 shadow-lg border border-gray-200 col-start-1 col-end-1 row-start-1 row-end-2", # start hidden if javascript is enabled - "[@media(scripting:enabled)]:opacity-0 [@media(scripting:enabled){[data-phx-main]_&}]:opacity-100", + "[@media(scripting:enabled)]:opacity-0 [@media(scripting:enabled)]:[[data-phx-main]_&]:opacity-100", # used to hide the disconnected flashes if(assigns[:rest][:hidden] == true, do: "hidden", else: "flex"), # override styles per severity assigns[:kind] == :info && "bg-white text-black", - assigns[:kind] == :error && "!text-red-700 !bg-red-100 border-red-200" + assigns[:kind] == :error && "!text-red-700 !bg-red-100 !border-red-200" ] end end @@ -477,15 +499,15 @@ need to override [`toast_class_fn/1`](https://hexdocs.pm/live_toast/LiveToast.ht def custom_toast_class_fn(assigns) do [ # base classes - "bg-white group/toast z-100 pointer-events-auto relative w-full items-center justify-between origin-center overflow-hidden rounded-lg p-4 shadow-lg border col-start-1 col-end-1 row-start-1 row-end-2", + "bg-white group/toast z-[100] pointer-events-auto relative w-full items-center justify-between origin-center overflow-hidden rounded-lg p-4 shadow-lg border border-gray-200 col-start-1 col-end-1 row-start-1 row-end-2", # start hidden if javascript is enabled - "[@media(scripting:enabled)]:opacity-0 [@media(scripting:enabled){[data-phx-main]_&}]:opacity-100", + "[@media(scripting:enabled)]:opacity-0 [@media(scripting:enabled)]:[[data-phx-main]_&]:opacity-100", # used to hide the disconnected flashes if(assigns[:rest][:hidden] == true, do: "hidden", else: "flex"), # override styles per severity assigns[:kind] == :info && "text-black", - assigns[:kind] == :error && "!text-red-700 !bg-red-100 border-red-200", - assigns[:kind] == :warning && "!text-amber-700 !bg-amber-100 border-amber-200" + assigns[:kind] == :error && "!text-red-700 !bg-red-100 !border-red-200", + assigns[:kind] == :warning && "!text-amber-700 !bg-amber-100 !border-amber-200" ] end ``` diff --git a/assets/js/live_toast/live_toast.ts b/assets/js/live_toast/live_toast.ts index 2b373f4..4c186f7 100644 --- a/assets/js/live_toast/live_toast.ts +++ b/assets/js/live_toast/live_toast.ts @@ -431,6 +431,7 @@ export function createLiveToastHook(duration = 6000, maxItems = 3) { // (don't want to do this quite yet because 1.0 is pretty new) // also repeat this on hide. + this.el.removeAttribute('hidden') this.el.style.display = 'flex' }) diff --git a/assets/test/live_toast.test.ts b/assets/test/live_toast.test.ts index a02b9e5..cf0181b 100644 --- a/assets/test/live_toast.test.ts +++ b/assets/test/live_toast.test.ts @@ -117,6 +117,31 @@ function mountToast(duration: number | 'Infinity' = 1000, countdown = false) { } } +function mountConnectionToast() { + document.body.innerHTML = ` + + ` + + const el = document.getElementById('client-error') as HTMLElement + const callbacks = createLiveToastHook() + const hook = { + el, + pushEvent: () => undefined, + pushEventTo: () => undefined + } + + callbacks.mounted.call(hook as never) + + return { el } +} + function mountToastGroup() { document.body.insertAdjacentHTML( 'beforeend', @@ -225,6 +250,17 @@ describe('LiveToast timed dismissal', () => { expect(toast.pushes).toHaveLength(0) }) + test('removes the hidden attribute before showing a connection error', async () => { + const toast = mountConnectionToast() + + toast.el.dispatchEvent(new Event('show-error')) + advance(0) + await settle() + + expect(toast.el.hidden).toBe(false) + expect(toast.el.style.display).toBe('flex') + }) + test('cancels dismissal when the hook is destroyed', () => { const toast = mountToast() diff --git a/demo/assets/css/app.css b/demo/assets/css/app.css index 378c8f9..b6c2dda 100644 --- a/demo/assets/css/app.css +++ b/demo/assets/css/app.css @@ -1,5 +1,13 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; +@import "tailwindcss" source(none); +@source "../css"; +@source "../js"; +@source "../../../assets"; +@source "../../../lib"; +@source "../../lib/demo_web"; + +@custom-variant phx-no-feedback (.phx-no-feedback&, .phx-no-feedback &); +@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &); +@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &); +@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &); /* This file is for your main application CSS */ diff --git a/demo/assets/tailwind.config.cjs b/demo/assets/tailwind.config.cjs deleted file mode 100644 index 1a78a16..0000000 --- a/demo/assets/tailwind.config.cjs +++ /dev/null @@ -1,45 +0,0 @@ -// See the Tailwind configuration guide for advanced usage -// https://tailwindcss.com/docs/configuration - -let plugin = require('tailwindcss/plugin') - -module.exports = { - content: [ - './js/**/*.js', - '../lib/*_web.ex', - '../lib/*_web/**/*.*ex', - '../../lib/**/*.*ex', - '../../assets/**/*.js', - '../../assets/**/*.ts', - ], - theme: { - extend: {}, - }, - plugins: [ - require('@tailwindcss/forms'), - plugin(({ addVariant }) => - addVariant('phx-no-feedback', [ - '&.phx-no-feedback', - '.phx-no-feedback &', - ]), - ), - plugin(({ addVariant }) => - addVariant('phx-click-loading', [ - '&.phx-click-loading', - '.phx-click-loading &', - ]), - ), - plugin(({ addVariant }) => - addVariant('phx-submit-loading', [ - '&.phx-submit-loading', - '.phx-submit-loading &', - ]), - ), - plugin(({ addVariant }) => - addVariant('phx-change-loading', [ - '&.phx-change-loading', - '.phx-change-loading &', - ]), - ), - ], -} diff --git a/demo/config/config.exs b/demo/config/config.exs index 54e6200..e79fed4 100644 --- a/demo/config/config.exs +++ b/demo/config/config.exs @@ -31,14 +31,13 @@ config :logger, :console, config :phoenix, :json_library, Jason config :tailwind, - version: "3.4.0", + version: "4.3.3", demo: [ args: ~w( - --config=tailwind.config.cjs - --input=css/app.css - --output=../priv/static/assets/app.css - ), - cd: Path.expand("../assets", __DIR__) + --input=assets/css/app.css + --output=priv/static/assets/app.css + ), + cd: Path.expand("..", __DIR__) ] import_config "#{config_env()}.exs" diff --git a/demo/lib/demo_web/components/core_components.ex b/demo/lib/demo_web/components/core_components.ex index d95f511..8865aea 100644 --- a/demo/lib/demo_web/components/core_components.ex +++ b/demo/lib/demo_web/components/core_components.ex @@ -14,7 +14,7 @@ defmodule DemoWeb.CoreComponents do