ruby-on-railsruby-on-rails-7hotwire-rails

How to highlight active nav link when using hotwire


I want to highlight menu link for current page. What is the best way of doing this?

For instance

  <!-- navigation -->
  <nav class="nav font-semibold text-md">
    <ul class="flex items-center">
      <li class="p-4 border-b-2 border-blue-600 border-opacity-0 hover:border-opacity-100 hover:text-blue-600 duration-200 cursor-pointer <%= current? "border-opacity-100", posts_path %>">
        <%= link_to 'Posts', posts_path, data: { turbo_frame: :main, turbo_action: 'replace' } %>
      </li>
      <li class="p-4 border-b-2 border-blue-600 border-opacity-0 hover:border-opacity-100 hover:text-blue-600 duration-200 cursor-pointer <%= current? "border-opacity-100", users_path %>">
        <%= link_to 'Users', users_path, data: { turbo_frame: :main, turbo_action: 'replace' } %>
      </li>
    </ul>
  </nav>
# helper
def current?(key, path)
 "#{key}" if current_page? path
end

doesn't work properly because the header leaves intact. And ofcourse if I remove data: { turbo_frame: :main, turbo_action: 'replace' } it works correctly. So how to make this working? If anyone could suggest totally different approach which is better, you are welcome


Solution

  • I don't think you can really leverage Rails too much here since the nav links don't get re-rendered by Rails. That is unless you want to wrap the navigation in its own turbo frame or something, but that gets pretty complicated really fast...

    What you could do instead is use Rails to determine the initial active link on page load and then update with JavaScript when clicking on the other links, or use JavaScript for the whole thing. Since the links don't get re-rendered, you don't need to worry about the link's url; whatever was clicked on last should be the active tab. If, however, you encounter a situation where that isn't true, you can determine the active link based on the src attribute of the turbo frame.

    A simple Stimulus controller could look something like this:

    // javascript/controllers/navbar_controller.js
    navigate(e) {
      const activeButton = this.element.querySelector('.active')
        
      if (activeButton) {
        activeButton.classList.remove('active')
      }
    
      e.target.classList.add('active')
    }
    

    And the HTML (slightly simplified here to focus on the important bits):

    <nav data-controller="navbar">
      <%= link_to "Posts", posts_path, 
          class: ["class1 class2 etc", { active: current_page?(posts_path) }],
          data: { turbo_frame: :main, action: "navbar#navigate" } %>
      <%= link_to "Users", users_path,
          class: ["class1 class2 etc", { active: current_page?(users_path) }],
          data: { turbo_frame: :main, action: "navbar#navigate" } %>
    </nav>