Skip to content

Instantly share code, notes, and snippets.

@danielvlopes
Created May 15, 2024 03:40
Show Gist options
  • Save danielvlopes/4da43ff07d6efea7aafb51093b6675c3 to your computer and use it in GitHub Desktop.
Save danielvlopes/4da43ff07d6efea7aafb51093b6675c3 to your computer and use it in GitHub Desktop.
<!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->
<button type="button" class="bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" 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 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
</button>
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["button", "span", "input"];
toggle() {
const isEnabled = this.buttonTarget.getAttribute("aria-checked") === "true";
this.buttonTarget.classList.remove(
isEnabled
? this.buttonTarget.dataset["onClass"]
: this.buttonTarget.dataset["offClass"],
);
this.buttonTarget.classList.add(
isEnabled
? this.buttonTarget.dataset["offClass"]
: this.buttonTarget.dataset["onClass"],
);
console.log(this.buttonTarget.classList);
this.buttonTarget.setAttribute("aria-checked", String(!isEnabled));
this.spanTarget.classList.toggle("translate-x-5", !isEnabled);
this.spanTarget.classList.toggle("translate-x-0", isEnabled);
this.inputTarget.value = !isEnabled ? "true" : "false";
}
}
module SwitchHelper
# Creates a toggle switch button with dynamic styling and data attributes.
# Example:
# <%= switch_tag 'toggle_active', checked: true, on_class: 'bg-green-500', off_class: 'bg-red-500' %>
#
# @param name [String] the name attribute for the hidden input field.
# @param checked [Boolean] initial checked state of the switch.
# @param options [Hash] additional options passed to the switch_button method.
def switch_tag(name, checked: false, **options)
content_tag :div, data: { controller: "switch" } do
concat switch_button(checked, **options)
concat hidden_field_tag(name, checked ? "true" : "false", data: { switch_target: "input" })
end
end
# Integrates the toggle switch within a Rails form using the form builder.
# Example:
# <%= form_with model: @user do |f| %>
# <%= switch_field f, :active, checked: @user.active, on_class: 'bg-green-500', off_class: 'bg-red-500' %>
# <% end %>
#
# @param f [ActionView::Helpers::FormBuilder] the form builder instance.
# @param method [Symbol] the method name corresponding to the model attribute.
# @param checked [Boolean] initial checked state of the switch.
# @param options [Hash] additional options passed to the switch_button method.
def switch_field(f, method, checked: false, **options)
content_tag :div, data: { controller: "switch" } do
concat(switch_button(checked: checked, **options))
concat(f.hidden_field(method, value: checked ? "true" : "false", data: { switch_target: "input" }))
end
end
private
# Helper to generate the toggle button element.
# @param checked [Boolean] if the switch is checked.
# @param on_class [String] CSS class for 'on' state.
# @param off_class [String] CSS class for 'off' state.
# @param button_options [Hash] additional attributes for the button element.
def switch_button(checked, on_class: "bg-zinc-900", off_class: "bg-zinc-100", button_options: {})
initial_class = checked ? on_class : off_class
default_classes = "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 #{initial_class}"
classes = [default_classes, button_options.delete(:class)].compact.join(" ")
default_data = { switch_target: "button", action: "click->switch#toggle", on_class: on_class, off_class: off_class }
data = default_data.merge(button_options.delete(:data) || {})
button_options.merge!(type: "button", role: "switch", "aria-checked" => checked, data: data, class: classes)
button_tag(**button_options) do
concat(content_tag(:span, "Use setting", class: "sr-only")) if button_options[:sr_only]
concat(content_tag(:span, "", data: { switch_target: "span" }, aria: { hidden: true }, class: "translate-x-#{checked ? '5' : '0'} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"))
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment