KevinConti

KevinConti

How to toggle two sets of arbitrary tailwind classes for transitions?

I’m working with LiveView.JS for the first time. It’s one of the features that’s brought me back to LiveView on the frontend, and I feel like it was a critical piece that was missing for the community in earlier versions of the websocket-based paradigm.

By default, Phoenix comes installed with TailwindCSS. In Tailwind, transitions often occur via CSS classes. Take the following example:

<!-- When a menu is 'opened', it should receive the "block" class. When 'closed', it should receive the "hidden" class -->
<div id="mobile-menu" class="block sm:hidden">...</div>
<div id="desktop-menu" class="hidden sm:block">...</div>

This is a very common occurrence in Tailwind - transitions are almost exclusively driven via class addition and subtraction. When I went to try and apply this pattern with LiveView.JS, I was surprised to not see an obvious way to approach this.

The challenge with JS.toggle() is that the display property is directly applied via the style=display: attribute, where tailwind classes are invalid. This causes problems in the reactive example above, because the mobile menu could be toggled to be in the open state (style= display:block), and if the window is resized, this style will override the existing reactive behavior defined in tailwind (sm:hidden), causing both menus to be shown at once!

Of course, you could avoid this by using the JS.addClass and JS.removeClass functions, but that requires you to know the current state of the element to determine which function to call… meaning we’d need to keep state on the server, exactly what we are trying to avoid for something like a menu’s visibility!

I looked over the other functions available in LiveView.JS, but none seem to handle what I imagine must be an incredibly common use-case.

So, please help me out LiveView devs - am I missing something obvious? All I want is the ability to toggle two sets of arbitrary tailwind classes and let the state be contained entirely on the client via JavaScript. If I can do that, Tailwind will take care of everything else.

Thanks for your suggestions!

Most Liked

chrismccord

chrismccord

Creator of Phoenix

LiveBeats uses tailwind and toggles menu and such just fine from TailwindUI. Have you looked there?:

check the <.dropdown> in core components and the show_dropdown and hide_dropdown functions:
https://github.com/fly-apps/live_beats/blob/master/lib/live_beats_web/components/core_components.ex#L318-L336

al2o3cr

al2o3cr

The post originally tried to use single backquotes on separate lines to quote a whole block of code, which resulted in the code not getting quoted. I’ve replaced them with triple-backquotes ``` which work as the author intended.

JohnnyCurran

JohnnyCurran

Your example seems to be empty - was it a copy/paste that didn’t get into the post?

Also, have you seen JS.transition? It supports tailwind classes Phoenix.LiveView.JS — Phoenix LiveView v1.2.5

Last Post!

tubedude

tubedude

I tried this at first and the problem I was getting was that the menu showing up cut in half.

(cut in half sidebar)

The issue was on the style: "display block;". So I figured I could transition the internal elements without actually tagging them to hide/show. Hence my initial code. But since you recommended I reverted back to show/hide and I tried all display options. The usual suspects (inline, inline-block) did not solve it, but using flex did the trick to show the sidebar correctly. But I decided not going this route, as I liked having one tag to control the show/hide state, leaving all others to transition only.
For reference the code would be:
JS.show(to: "#menu", transition: {some transition}, display: "flex")

And now on the original problem that hiding the menu was not working:
I added a transition bringing down the opacity just a notch (100% to 90%) to the main element and left it on screen being able to show its inner elements transition.

The final code is as follows:

  defp close_mobile_menu() do
    %JS{}
    |> JS.transition(
      {"transition ease-in-out duration-300 transform", "translate-x-0", "-translate-x-full"},
      to: "#off-canvas-menu-mobile-inset",
      time: 300
    )
    |> JS.transition(
      {"ease-in-out duration-300", "opacity-100", "opacity-0"},
      to: "#off-canvas-menu-mobile-button",
      time: 300
    )
    |> JS.transition(
      {"transition-opacity ease-linear duration-300", "opacity-100", "opacity-0"},
      to: "#off-canvas-menu-mobile-backdrop",
      time: 300
    )
    |>JS.hide(
      transition: {"transition-opacity ease-linear duration-300", "opacity-100", "opacity-90"},
      to: "#off-canvas-menu-mobile",
      time: 300)
    |> JS.set_attribute({"aria-expanded", "false"}, to: "#off-canvas-menu-mobile")
  end

  defp open_mobile_menu() do
    %JS{}
    |> JS.transition(
      {"transition-opacity ease-linear duration-300", "opacity-0", "opacity-100"},
      to: "#off-canvas-menu-mobile-backdrop",
      time: 300
    )
    |> JS.transition(
      {"transition ease-in-out duration-300 transform", "-translate-x-full", "translate-x-0"},
      to: "#off-canvas-menu-mobile-inset",
      time: 300
    )
    |> JS.transition(
      {"ease-in-out duration-300", "opacity-0", "opacity-100"},
      to: "#off-canvas-menu-mobile-button",
      time: 300
    )
    |> JS.show(
      to: "#off-canvas-menu-mobile",
      time: 300)
  |> JS.set_attribute({"aria-expanded", "true"}, to: "#off-canvas-menu-mobile")
  end

In other words, thanks @cmo for the push to the right direction.

Where Next?

Popular in Questions Top

RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 49266 226
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42716 114
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

We're in Beta

About us Mission Statement