how to integrate tailwindui JS in rails app - ruby-on-rails

i'm on rails 7 with esbuild. I'm using tailwindUI. It works properly when using only css components. When a component uses JS it does not work anymore. For example the dropdown menu is open by default and i can't close it. I added require('#tailwindcss/ui')
in tailwind.config.js
Rails 7.0.0.alpha2
ruby 3.0.2p107
"#tailwindcss/ui": "^0.7.2"
Any idea?

I'm not sure if I fully understand your question, but I've experienced the same issue. The way I solved it was by using stimulus js tailwind components to add Javascript to the elements.
I ended up having to add a "hidden" tag to the class for each element to ensure that it wouldn't flash on screen when the page loaded due to the lag time between the HTML and JS rendering.
You can find them here:
https://github.com/excid3/tailwindcss-stimulus-components
Here is an example of the dropdown code.
If you've got stimulus installed you can do the following:
yarn add tailwindcss-stimulus-components
or
npm install tailwindcss-stimulus-components
then simply add the data-controller, data-action and data-target to the elements you need:
<div class="inline-block text-sm px-4 py-2 leading-none rounded no-underline text-gray hover:text-gray-900 hover:bg-white mt-4 lg:mt-0">
<div class="relative" data-controller="dropdown">
<div data-action="click->dropdown#toggle click#window->dropdown#hide" role="button" data-dropdown-target="button" tabindex="0" class="inline-block select-none">
<span class="appearance-none flex items-center inline-block text-gray-700">
<% if current_user %>
<%= image_tag avatar_url_for(current_user), class: "rounded-full h-8 w-8 align-middle" %>
<% end %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class="h-4 w-4"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"></path></svg>
</span>
</div>
<div data-dropdown-target="menu" class="absolute right-0 mt-2 hidden">
<div class="bg-white shadow rounded border overflow-hidden">
<%= link_to 'Profile', edit_user_registration_path, data: {action: "click->dropdown#toggle"}, class: 'no-underline block pl-8 py-3 text-gray-900 bg-white hover:bg-gray-300 whitespace-nowrap' %>
<%= link_to 'Password', password_path, data: {action: "click->dropdown#toggle"}, class: 'no-underline block px-8 py-3 text-gray-900 bg-white hover:bg-gray-300 whitespace-nowrap' %>
<%= link_to 'Accounts', user_connected_accounts_path, data: {action: "click->dropdown#toggle"}, class: 'no-underline block px-8 py-3 text-gray-900 bg-white hover:bg-gray-300 whitespace-nowrap' %>
<%= link_to 'Billing', subscription_path, data: {action: "click->dropdown#toggle"}, class: 'no-underline block px-8 py-3 text-gray-900 bg-white hover:bg-gray-300 whitespace-nowrap' %>
<%= link_to 'Sign Out', destroy_user_session_path, method: :delete, data: {action: "click->dropdown#toggle"}, class: 'no-underline block px-8 py-3 border-t text-gray-900 bg-white hover:bg-gray-300 whitespace-nowrap' %>
</div>
</div>
</div>
</div>

I got Tailwind UI components that require JavaScript working with Rails 7 on esbuild by adding alpine.js to my app. https://alpinejs.dev/start-here
Alpine.js can be used for animating dropdowns, toggles, navbars, etc... and the TailwindUI documentation uses it in some example code.
you can either use it via the CDN or install as a package.
if you use it as a CDN then just put the current version number in the URL and add the script tag above your code.
<script defer src="https://unpkg.com/alpinejs#3.7.1/dist/cdn.min.js"></script>
<div x-data="{ open: false }">
<button type="button" #click="open = ! open" class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Button text
</button>
<div x-show="open" #click.outside="open = false">Contents...</div>
</div>
I opted to install the npm packages alpinejs and alpine-turbo-drive-adapter so that I didn't have to add the script tag all over my views. In app/javascript/controllers/applications.js I added the import statements.
import { Application } from "#hotwired/stimulus"
import Alpine from 'alpinejs'
import 'alpine-turbo-drive-adapter'
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
window.Alpine = Alpine
window.Alpine = Alpine
Alpine.start()
export { application }
another thing to note is that you can't just drop the TailwindUI components that require JavaScript into your code like with Bootstrap. You have to checkout the comments in the TailwindUI code and configure Apline.js to do what they recommend.
for example, if you check out the "simple toggle" the comments say what to change to make the toggle enabled/disabled https://tailwindui.com/components/application-ui/forms/toggles
<!-- This example requires Tailwind CSS v2.0+ -->
<!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->
<button type="button" class="bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" role="switch" aria-checked="false">
<span class="sr-only">Use setting</span>
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
<span aria-hidden="true" class="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
</button>
if you checkout the TailwindUI documentation https://tailwindui.com/documentation#using-html-and-your-own-js they give an example of how to use alpine.js to make the toggle functionality work. Essentially you just have to toggle the x-data attribute isOn from true to false so that menus, dropdown, etc... open and close.
<span
x-data="{ isOn: false }"
#click="isOn = !isOn"
:aria-checked="isOn"
:class="{'bg-indigo-600': isOn, 'bg-gray-200': !isOn }"
class="bg-gray-200 relative inline-block flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline"
role="checkbox"
tabindex="0"
>
<span
aria-hidden="true"
:class="{'translate-x-5': isOn, 'translate-x-0': !isOn }"
class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"
></span>
</span>

thanks #Marco
the answer was in the doc. When you use html you'll needs to write your custom JS if you want to make it work

Related

Initialize Flowbite components on Ruby on Rails turbo:frame-load event

I am using Flowbite dropdown component with flowbite.turbo.js.
The problem is that after the frame is rendered, Flowbite is not reloaded and the dropdown do not open.
How can I re-initiliaze Flowbite components after the turbo:frame-load event?
application.js
import 'flowbite';
import "flowbite/dist/flowbite.turbo.js";
index.html.erb
<%=turbo_frame_tag "example" do %>
<!-- DROPDOWN FLOWBITE THAT DO NOT OPENS -->
<button id="dropdownDefaultButton" data-dropdown-toggle="dropdown" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" type="button">Dropdown button <svg class="w-4 h-4 ml-2" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg></button>
<!-- Dropdown menu -->
<div id="dropdown" class="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700">
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownDefaultButton">
<li>
Dashboard
</li>
</ul>
</div>
<% end %>
example.html.erb
<%=turbo_frame_tag "example", src: url_for(action: :example), loading: 'lazy' do %>
Loading...
<% end %>

Rendering layout with local variable in rails

In my rails controller I have an action that renders a specific layout:
def registrations_for_user
render layout: 'remote_modal', locals: { modal_title: I18n.t('organizations.enrolled_in_learning_item.title') }
end
This layout needs the local modal_title to work, that is why I am passing it up there. Here's the layout :
<div class="relative px-4 w-full max-w-6xl md:h-auto">
<!-- Modal content -->
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<!-- Modal header -->
<div class="w-full">
<div class="flex justify-between items-start p-5 rounded-t">
<div class="w-full-0">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
<%= modal_title %>
</h3>
</div>
<i class="close-modal fas fa-times text-lg cursor-pointer"></i>
</div>
</div>
<!-- Modal body -->
<div class="px-6">
<div class="w-full">
<%= yield %>
</div>
</div>
</div>
However I keep getting :
NameError - undefined local variable or method `modal_title' for #<ActionView::Base:0x000000000656f8>:
app/views/layouts/remote_modal.html.erb:9
Is it not possible to pass a local to a layout in rails ? Is there another way to do it ? I've read answers that suggested using a #variable but that does not seem like a clean way to do it.

Bootstrap 5 Aligning Items In Navbar

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<%= link_to "My Ozone", root_path, class: "navbar-brand" %>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarSupportedContent">
<%= form_tag zipcode_path, :method => 'POST' do %>
<div class="form-inline my-2 my-lg-0">
<%= text_field_tag 'zipcode', nil, placeholder: "Search Zipcode", class: "form-control mr-sm-2" %>
<%= submit_tag 'Search', class: 'btn btn-outline-secondary my-2 my-sm-0' %>
</div>
<% end %>
</div>
</nav>
I've finished a Ruby on Rails project that displays ozone information based on the zip code searched for. However, I can't seem to get the field and submit button next to each other. I've tried a variety of different things like changing the div class and wrapping things in a form tag, but it doesn't seem to resolve the issue. Looking for help with getting the items aligned on the same row.
You need to use d-flex instead of form-inline class in Bootstrap 5.
Dropped form-specific layout classes for our grid system in Bootstrap 5. Use our grid
and utilities instead of .form-group, .form-row, or .form-inline - more details here
Your code should be as below:
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<%= link_to "My Ozone", root_path, class: "navbar-brand" %>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarSupportedContent">
<%= form_tag zipcode_path, :method => 'POST' do %>
<div class="d-flex my-2 my-lg-0">
<%= text_field_tag 'zipcode', nil, placeholder: "Search Zipcode", class: "form-control mr-sm-2" %>
<%= submit_tag 'Search', class: 'btn btn-outline-secondary my-2 my-sm-0' %>
</div>
<% end %>
</div>
</nav>

Rails 6 Show or Hide a div if the cookie value matches a token in database

I am building a rails 6 application, and have the requirement that I have to show a banner with a "cookie acceptance".
I have built said banner and when they click the accept button a cookie is created with an "acceptance token", saved to the database and displayed in the cookie.
What I am trying to achieve is to hide that banner if the cookie is present, and the cookie token mates record in the database.
I am stuck on how to call the cookie and match the value against the database. Any assistance here would be great.. cookies are brand new to my world!
my cookie create method: - WORKS
def create
#cookie_acceptance = CookieAcceptance.new(cookie_acceptance_params)
params[:ip_address] = request.remote_ip
return unless #cookie_acceptance.save
cookies[:roadze_cookie_acceptance] = {
value: #cookie_acceptance.accept_token,
expires: 1.year.from_now
}
end
My banner:
<div class="fixed bottom-0 inset-x-0 pb-2 sm:pb-5" id="cookieBanner">
<div class="max-w-screen-xl mx-auto px-2 sm:px-6 lg:px-8">
<div class="p-2 rounded-lg bg-blue-600 shadow-lg sm:p-3">
<div class="flex items-center justify-between flex-wrap">
<div class="w-0 flex-1 flex items-center">
<span class="flex p-2 rounded-lg bg-blue-800">
<div class="flex items-center justify-center h-6 w-6 text-white">
<i class="far fa-question fa-1x"></i>
</div>
</span>
<p class="ml-3 font-medium text-white truncate">
<span class="md:hidden">
This site uses cookies to help your experience.
</span>
<span class="hidden md:inline">
Attention: <span class="logo-font font-bold">roadze<span class="text-xs">.io</span></span> uses cookies to help your overall user experience, and to allow us to offer products specific to your region. <%= link_to "View cookie policy", '#', class: "text-white ml-3 hover:underline" %>
</span>
</p>
</div>
<div class="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto">
<div class="rounded-md shadow-sm">
<%= form_for(#cookie_acceptance, remote: true) do |ca| %>
<%= ca.hidden_field :ip_address, value: params[:ip_address] %>
<%= ca.submit 'Accept', class: 'flex items-center justify-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-blue-600 bg-white hover:text-blue-500 focus:outline-none focus:shadow-outline transition ease-in-out duration-150' %>
<% end %>
</div>
</div>
<div class="order-2 flex-shrink-0 sm:order-3 sm:ml-2">
<button type="button" class="-mr-1 flex p-2 rounded-md hover:bg-blue-500 focus:outline-none focus:bg-blue-500 transition ease-in-out duration-150" aria-label="Dismiss">
<svg class="h-6 w-6 text-white" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
You should stop rendering the partial itself like below(notice condition at the end)
<div>
...
<%= render 'global/marketing/cookie_banner', cookie_acceptance: at_symbol cookie_acceptance if cookie_accepted? %>
...
</div>
and you should have helper method in ApplicationHelper or relevant helper
def cookie_accepted?
CookieAcceptance.exists?(accept_token: cookies[:roadze_cookie_acceptance])
end
if this check is required in controllers as well, you can move above method from helper to ApplicationController and mark this method as helper_method. This way you will be able to use cookie_accepted? across views and controllers.
So this may be a bit "hackie", however I achieved my objective by setting the following in my application_controller
before_action :validate_cookie_presnece
def validate_cookie_presnece
return unless cookies.present?
#cookie = CookieAcceptance.find_by(accept_token: cookies[:roadze_cookie_acceptance]).present?
end
all seems to be working well!

How can I use bootstrap's responsive design without all the repeating code?

Perhaps I'm just not seeing something that would easily correct this; however, I find that when I'm using bootstrap's responsive design i tend to repeat code sometimes. An example of what I'm talking about is shown below. How can I code this example without repeating code and yet keeping the responsive design?
<div class="col-xs-12 col-sm-9 col-md-6">
<div class="row">
<div class="col-sm-3 text-left hidden-xs hidden-sm visible-md visible-lg">
<%= image_tag(#club.logo_image) %>
</div>
<div class="col-sm-3 text-left hidden-xs visible-sm hidden-md hidden-lg">
<%= image_tag(#club.logo_image) %>
</div>
<div class="col-sm-3 text-center visible-xs hidden-sm hidden-md hidden-lg">
<%= image_tag(#club.logo_image) %>
</div>
<div class="col-sm-9 text-left hidden-xs hidden-sm visible-md visible-lg">
<h2><%= #club.name %></h2>
<h4><%= #club.stadium_name %> - <%= #club.division_and_conference %></h4>
</div>
<div class="col-sm-9 text-left hidden-xs visible-sm hidden-md hidden-lg">
<h2><%= #club.name %></h2>
<h4><%= #club.stadium_name %> - <%= #club.division_and_conference %></h4>
</div>
<div class="col-sm-9 text-center visible-xs hidden-sm hidden-md hidden-lg">
<h2><%= #club.name %></h2>
<h4><%= #club.stadium_name %></h4>
<h4><%= #club.division_and_conference %></h4>
</div>
</div>
</div>
With using SCSS, you could create a class and extend the other classes.
<div class="resp-col-sm-3">
<%= image_tag(#club.logo_image) %>
</div>
And the class would look something like:
.resp-col-sm-3 {
#extend .col-sm-3;
#extend .text-left;
#extend .hidden-xs;
#extend .hidden-sm;
#extend .visible-md;
#extend .visible-lg;
}
Another way is to break down responsiveness behavior into different less file and define class property based on the different media query.
<div class="col-sm-3 logo-image">
<%= image_tag(#club.logo_image) %>
</div>
Then based on http://www.tutorialspoint.com/bootstrap/bootstrap_responsive_utilities.htm
the div for "text-left visible-lg" will become
#media screen and (min-width: 922px) {
.logo-image{
...css property values
}
}
the div for "text-center visible-xs" will become
#media screen and (max-width: 768px) {
.logo-image{
...css property values
}
}
This is just my personal preference of organizing css styles based on different media query instead of leveraging bootstrap framework.

Resources