tailwind_theme_picker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f6b8bf4b275e9146174dd904158ed0a212c4be814070e8425fddceee8cd17829
4
+ data.tar.gz: 37df132c76d2f17771d8307edfb2f66fb0e3c47072cd6801f3c0f6f84c5d50dc
5
+ SHA512:
6
+ metadata.gz: aa50cef29ff1643fe124814939db157180b7569ebd152004276b64b16821a22084ca45e252c438368da34544d0ba92f849df71ee815dd98ccbcba3c3fd8d3c30
7
+ data.tar.gz: 375d7df84f0c51866e2423f5cb813b2f9ac689f95e6309a3ecf78431997a6d17144e9381c2addfd83962812e6747185c6f63d19f36ead61c556048f8c472f8a3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tayden Miller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # TailwindThemePicker
2
+
3
+ Drop-in theme + light/dark picker for Tailwind-based Rails apps.
4
+
5
+ ## What's in the box
6
+
7
+ - Floating Stimulus-powered picker UI (palette toggle, 27 color swatches, light/dark switch)
8
+ - Two-cookie persistence (`theme`, `mode`) — the server can render `<html class="theme-sky dark">` before any JS runs
9
+ - localStorage fallback so cross-tab updates and offline still feel snappy
10
+ - 27 ready-to-go `.theme-*` blocks targeting Tailwind's color palette
11
+ - Inline FOUC script that's only emitted when cookies aren't yet set
12
+
13
+ ## Install
14
+
15
+ ```ruby
16
+ # Gemfile
17
+ gem "tailwind_theme_picker", path: "../theme_picker" # or git: / version once published
18
+ ```
19
+
20
+ ```bash
21
+ bundle install
22
+ bin/rails g tailwind_theme_picker:install # copies themes.css into app/assets/tailwind/tailwind_theme_picker/
23
+ bin/rails g tailwind_theme_picker:install --initializer # also generate config/initializers/tailwind_theme_picker.rb
24
+ ```
25
+
26
+ Re-run the generator after upgrading the gem to pick up any new theme rules.
27
+
28
+ ### Configure (optional)
29
+
30
+ ```ruby
31
+ # config/initializers/tailwind_theme_picker.rb
32
+ TailwindThemePicker.configure do |c|
33
+ c.themes = %w[red blue green] # subset or extend the default 27
34
+ c.default = "blue"
35
+ end
36
+ ```
37
+
38
+ ### Wire up the layout
39
+
40
+ ```slim
41
+ html lang="en" *theme_picker_html_attrs
42
+ head
43
+ / ...stylesheets, importmap...
44
+ = theme_picker_fouc_script
45
+ body
46
+ = render_theme_picker
47
+ = yield
48
+ ```
49
+
50
+ ### Import the CSS
51
+
52
+ ```css
53
+ /* app/assets/tailwind/application.css */
54
+ @import 'tailwindcss';
55
+
56
+ /* ...your other rules... */
57
+
58
+ @import './tailwind_theme_picker/themes';
59
+ ```
60
+
61
+ The gem's CSS rules are intentionally unlayered so they win over a `:root { --color-primary: ... }` default inside `@layer base`. Keep this import outside any `@layer` block.
62
+
63
+ Tailwind needs `darkMode: 'class'` in your config. Your app should also define the `--color-primary` custom property and any utilities (`.bg-app`, `.btn-primary`, etc.) that consume it.
64
+
65
+ ### Remove your old controller
66
+
67
+ If you previously had `app/javascript/controllers/theme_controller.js`, delete it — the gem pins one at the same import name.
68
+
69
+ ## How persistence works
70
+
71
+ 1. Stimulus controller writes both cookies on every change.
72
+ 2. On the next request, `theme_picker_html_attrs` reads them and paints `<html>` server-side. No FOUC.
73
+ 3. On the user's *first* visit (no cookies), `theme_picker_fouc_script` emits ~250 bytes of inline JS that paints from localStorage or system preference. After that, cookies take over and the helper returns an empty string.
74
+
75
+ ## Configuration reference
76
+
77
+ | Setting | Default | Notes |
78
+ |------------------|------------------------------|-------|
79
+ | `themes` | 27-color rainbow | Must match `.theme-*` rules you actually ship. |
80
+ | `default` | `"sky"` | Used when cookie is absent or refers to an unknown theme. |
81
+ | `theme_cookie` | `"theme"` | |
82
+ | `mode_cookie` | `"mode"` | |
83
+ | `cookie_max_age` | one year | Seconds. |
84
+
@@ -0,0 +1,73 @@
1
+ module TailwindThemePicker
2
+
3
+ module ViewHelper
4
+
5
+ # Emoji defaults so the gem has no icon-library dependency. Override by
6
+ # passing :icons to render_theme_picker.
7
+ DEFAULT_ICONS = {
8
+ palette: "🎨",
9
+ times: "❌",
10
+ sun: "☀️",
11
+ moon: "🌙"
12
+ }.freeze
13
+
14
+ # Returns HTML attributes for the <html> tag based on the request's cookies.
15
+ # Use like: html lang="en" *theme_picker_html_attrs
16
+ def theme_picker_html_attrs
17
+ config = TailwindThemePicker.configuration
18
+ theme = cookies[config.theme_cookie].presence
19
+ theme = config.default unless config.themes.include?(theme)
20
+ mode = cookies[config.mode_cookie] == "dark" ? "dark" : nil
21
+
22
+ classes = ["theme-#{theme}", mode].compact.join(" ")
23
+ { class: classes }
24
+ end
25
+
26
+ # Whether the current request already has theme cookies set. Useful for
27
+ # skipping the FOUC fallback script.
28
+ def theme_picker_cookies_present?
29
+ config = TailwindThemePicker.configuration
30
+ cookies[config.theme_cookie].present? && cookies[config.mode_cookie].present?
31
+ end
32
+
33
+ # Inline <script> that paints theme + mode classes from cookies/localStorage
34
+ # before the rest of the page renders. Only needed on the first visit (when
35
+ # the cookies aren't yet set server-side) — pass force: true to always emit.
36
+ def theme_picker_fouc_script(force: false)
37
+ return "".html_safe if !force && theme_picker_cookies_present?
38
+
39
+ config = TailwindThemePicker.configuration
40
+ nonce = (respond_to?(:content_security_policy_nonce) ? content_security_policy_nonce : nil)
41
+
42
+ js = <<~JS
43
+ (function(){try{
44
+ var d=document.documentElement;
45
+ var rc=function(n){var m=document.cookie.match(new RegExp('(?:^|; )'+n+'=([^;]*)'));return m?decodeURIComponent(m[1]):null};
46
+ var tn=rc('#{config.theme_cookie}')||localStorage.getItem('theme')||'#{config.default}';
47
+ d.classList.add('theme-'+tn);
48
+ var m=rc('#{config.mode_cookie}')||localStorage.getItem('mode');
49
+ if(m==='dark'||(!m&&window.matchMedia('(prefers-color-scheme: dark)').matches)){d.classList.add('dark');}else{d.classList.remove('dark');}
50
+ }catch(e){}})();
51
+ JS
52
+
53
+ content_tag(:script, js.html_safe, nonce: nonce)
54
+ end
55
+
56
+ def render_theme_picker(themes: nil, default: nil, icons: {})
57
+ config = TailwindThemePicker.configuration
58
+ render(
59
+ partial: "tailwind_theme_picker/picker",
60
+ locals: {
61
+ themes: (themes || config.themes).map(&:to_s),
62
+ default: (default || config.default).to_s,
63
+ theme_cookie: config.theme_cookie,
64
+ mode_cookie: config.mode_cookie,
65
+ cookie_max_age: config.cookie_max_age,
66
+ icons: DEFAULT_ICONS.merge(icons)
67
+ }
68
+ )
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,111 @@
1
+ /*
2
+ TailwindThemePicker Stimulus controller.
3
+
4
+ Persists user choice in TWO cookies (theme, mode) so the server can render the
5
+ matching classes on <html> before any JS runs. localStorage is kept as a tab-local
6
+ cache so cross-tab updates and offline still work.
7
+
8
+ If you change cookie names here, update TailwindThemePicker::Configuration and any
9
+ FOUC script the host emits.
10
+ */
11
+
12
+ import { Controller } from "@hotwired/stimulus"
13
+
14
+ export default class extends Controller {
15
+
16
+ static values = {
17
+ themes: { type: Array, default: [] },
18
+ defaultTheme: { type: String, default: "" },
19
+ theme: { type: String, default: "" },
20
+ darkMode: { type: Boolean, default: false },
21
+ themeCookie: { type: String, default: "theme" },
22
+ modeCookie: { type: String, default: "mode" },
23
+ cookieMaxAge: { type: Number, default: 31536000 }
24
+ }
25
+
26
+ static targets = [ "toggle", "panel", "mode" ]
27
+
28
+ connect() {
29
+ this.themeValue = this.defaultThemeValue || this.themesValue[0]
30
+
31
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches
32
+
33
+ // Cookie wins over localStorage (server painted html based on cookie).
34
+ const cookieTheme = this.readCookie(this.themeCookieValue)
35
+ const cookieMode = this.readCookie(this.modeCookieValue)
36
+
37
+ try {
38
+ this.themeValue = cookieTheme || localStorage.getItem("theme") || this.themeValue
39
+ try { localStorage.setItem("theme", this.themeValue) } catch(_) { }
40
+
41
+ const storedMode = cookieMode || localStorage.getItem("mode")
42
+ this.darkModeValue = storedMode === null ? prefersDark : storedMode === "dark"
43
+ try { localStorage.setItem("mode", this.darkModeValue ? "dark" : "light") } catch(_) { }
44
+ } catch(_) {}
45
+
46
+ if (!this.themesValue.includes(this.themeValue)) this.themeValue = this.defaultThemeValue
47
+
48
+ // Backfill cookies in case this is the user's first visit.
49
+ this.writeCookie(this.themeCookieValue, this.themeValue)
50
+ this.writeCookie(this.modeCookieValue, this.darkModeValue ? "dark" : "light")
51
+
52
+ this.applyThemeClass(this.themeValue)
53
+
54
+ if (this.darkModeValue) {
55
+ document.documentElement.classList.add("dark")
56
+ this.swapIcon(this.modeTarget)
57
+ } else
58
+ document.documentElement.classList.remove("dark")
59
+ }
60
+
61
+ togglePanel() {
62
+ this.panelTarget.classList.toggle("hidden")
63
+ this.swapIcon(this.toggleTarget)
64
+ }
65
+
66
+ toggleMode() {
67
+ this.darkModeValue = !this.darkModeValue
68
+ document.documentElement.classList.toggle("dark")
69
+ this.swapIcon(this.modeTarget)
70
+ const mode = this.darkModeValue ? "dark" : "light"
71
+ try { localStorage.setItem("mode", mode) } catch(_) { }
72
+ this.writeCookie(this.modeCookieValue, mode)
73
+ }
74
+
75
+ choose(event) {
76
+ const name = event.currentTarget.dataset.themeName
77
+ this.applyThemeClass(name)
78
+ this.themeValue = name
79
+ try { localStorage.setItem("theme", name) } catch(_) { }
80
+ this.writeCookie(this.themeCookieValue, name)
81
+ }
82
+
83
+ preview(event) {
84
+ this.applyThemeClass(event.currentTarget?.dataset.themeName)
85
+ }
86
+
87
+ cancelPreview() {
88
+ this.applyThemeClass(this.themeValue)
89
+ }
90
+
91
+ applyThemeClass(theme) {
92
+ document.documentElement.className = document.documentElement.className.replace(/theme-[a-z]+/g, "").trim()
93
+ document.documentElement.classList.add(`theme-${theme}`)
94
+ }
95
+
96
+ swapIcon(element) {
97
+ const current = element.innerHTML
98
+ element.innerHTML = element.dataset.icon
99
+ element.dataset.icon = current
100
+ }
101
+
102
+ readCookie(name) {
103
+ const match = document.cookie.match(new RegExp("(?:^|; )" + name.replace(/[.$?*|{}()[\]\\\/+^]/g, "\\$&") + "=([^;]*)"))
104
+ return match ? decodeURIComponent(match[1]) : null
105
+ }
106
+
107
+ writeCookie(name, value) {
108
+ document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${this.cookieMaxAgeValue}; SameSite=Lax`
109
+ }
110
+
111
+ }
@@ -0,0 +1,15 @@
1
+ .fixed.top-2.right-2.z-50.flex.flex-col.items-end.gap-2 data-controller="theme" data-theme-themes-value=themes data-theme-default-theme-value=default data-theme-theme-value="" data-theme-theme-cookie-value=theme_cookie data-theme-mode-cookie-value=mode_cookie data-theme-cookie-max-age-value=cookie_max_age
2
+
3
+ button.flex-center.w-8.aspect-square.border.border-base.rounded.btn-primary.text-fg.text-lg.cursor-pointer type="button" data-action="theme#togglePanel" data-theme-target="toggle" data-icon=icons[:times].to_s.gsub('"', "'")
4
+
5
+ = icons[:palette]
6
+
7
+ .flex.flex-row.items-end.flex-wrap.hidden.bg-surface.backdrop-blur-sm.rounded.p-2.shadow-lg.border.border-base.overflow-hidden data-theme-target="panel"
8
+
9
+ - themes.each do |name|
10
+
11
+ button.relative.flex-center.w-8.aspect-square.bg-primary/85.cursor-pointer class="theme-#{ name }" type="button" data-action="mouseenter->theme#preview mouseleave->theme#cancelPreview theme#choose" data-theme-name=name
12
+
13
+ button.flex-center.justify-center.w-8.aspect-square.border.border-base.rounded.btn-primary.text-sm.cursor-pointer.text-yellow-500.ml-2 class="text-shadow-[0px_0px_3px_black]" type="button" data-action="theme#toggleMode" data-theme-target="mode" data-icon=icons[:moon].to_s.gsub('"', "'")
14
+
15
+ = icons[:sun]
@@ -0,0 +1,41 @@
1
+ /*
2
+ TailwindThemePicker default themes. Each rule maps one `.theme-*` selector to a
3
+ primary color CSS custom property. Host apps consume this via Tailwind's
4
+ color system (e.g. `bg-primary` reads `var(--color-primary)`).
5
+
6
+ These rules are intentionally UNLAYERED. Hosts typically define their default
7
+ `--color-primary` inside `@layer base { :root { ... } }`. Unlayered rules win
8
+ the cascade over any @layer rule of equal specificity, so `.theme-red` always
9
+ overrides `:root` regardless of where the host @imports this file.
10
+
11
+ To add or override a theme, define another `.theme-<name>` rule (also
12
+ unlayered) AFTER this import in your stylesheet, and pass the name to
13
+ TailwindThemePicker.configure so it appears in the picker UI.
14
+ */
15
+
16
+ .theme-red { --color-primary: var(--color-red-500); }
17
+ .theme-orange { --color-primary: var(--color-orange-500); }
18
+ .theme-amber { --color-primary: var(--color-amber-500); }
19
+ .theme-yellow { --color-primary: var(--color-yellow-500); }
20
+ .theme-lime { --color-primary: var(--color-lime-500); }
21
+ .theme-green { --color-primary: var(--color-green-500); }
22
+ .theme-emerald { --color-primary: var(--color-emerald-500); }
23
+ .theme-teal { --color-primary: var(--color-teal-500); }
24
+ .theme-cyan { --color-primary: var(--color-cyan-500); }
25
+ .theme-sky { --color-primary: var(--color-sky-500); }
26
+ .theme-blue { --color-primary: var(--color-blue-500); }
27
+ .theme-indigo { --color-primary: var(--color-indigo-500); }
28
+ .theme-violet { --color-primary: var(--color-violet-500); }
29
+ .theme-purple { --color-primary: var(--color-purple-500); }
30
+ .theme-fuchsia { --color-primary: var(--color-fuchsia-500); }
31
+ .theme-pink { --color-primary: var(--color-pink-500); }
32
+ .theme-rose { --color-primary: var(--color-rose-500); }
33
+ .theme-slate { --color-primary: var(--color-slate-400); }
34
+ .theme-gray { --color-primary: var(--color-gray-400); }
35
+ .theme-zinc { --color-primary: var(--color-zinc-400); }
36
+ .theme-neutral { --color-primary: var(--color-neutral-400); }
37
+ .theme-stone { --color-primary: var(--color-stone-400); }
38
+ .theme-taupe { --color-primary: var(--color-taupe-400); }
39
+ .theme-mauve { --color-primary: var(--color-mauve-400); }
40
+ .theme-mist { --color-primary: var(--color-mist-400); }
41
+ .theme-olive { --color-primary: var(--color-olive-400); }
@@ -0,0 +1 @@
1
+ pin "controllers/theme_controller", to: "tailwind_theme_picker/controllers/theme_controller.js"
@@ -0,0 +1,52 @@
1
+ require "rails/generators/base"
2
+
3
+ module TailwindThemePicker
4
+
5
+ module Generators
6
+
7
+ class InstallGenerator < Rails::Generators::Base
8
+
9
+ source_root File.expand_path("../../../..", __dir__)
10
+
11
+ desc "Copies tailwind_theme_picker themes.css into app/assets/tailwind/tailwind_theme_picker/ and prints layout wiring instructions."
12
+
13
+ class_option :initializer, type: :boolean, default: false,
14
+ desc: "Also create config/initializers/tailwind_theme_picker.rb"
15
+
16
+ def copy_themes_stylesheet
17
+ copy_file "assets/stylesheets/tailwind_theme_picker/themes.css",
18
+ "app/assets/tailwind/tailwind_theme_picker/themes.css"
19
+ end
20
+
21
+ def create_initializer
22
+ return unless options[:initializer]
23
+ create_file "config/initializers/tailwind_theme_picker.rb", <<~RUBY
24
+ TailwindThemePicker.configure do |c|
25
+ # c.themes = %w[red blue green] # subset / extend default 27
26
+ # c.default = "sky"
27
+ # c.theme_cookie = "theme"
28
+ # c.mode_cookie = "mode"
29
+ # c.cookie_max_age = 60 * 60 * 24 * 365
30
+ end
31
+ RUBY
32
+ end
33
+
34
+ def show_post_install
35
+ say "\nTailwindThemePicker installed.", :green
36
+ say ""
37
+ say "Next steps:"
38
+ say " 1. Add to your Tailwind input CSS (e.g. app/assets/tailwind/application.css):"
39
+ say " @import './tailwind_theme_picker/themes';", :cyan
40
+ say " 2. Wire up your layout <html> tag and body:"
41
+ say " html lang=\"en\" *theme_picker_html_attrs", :cyan
42
+ say " = theme_picker_fouc_script # in <head>"
43
+ say " = render_theme_picker # in <body>"
44
+ say ""
45
+ say "After upgrading the gem, re-run this generator to pick up any new themes."
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,23 @@
1
+ module TailwindThemePicker
2
+
3
+ class Configuration
4
+
5
+ attr_accessor :themes, :default, :theme_cookie, :mode_cookie, :cookie_max_age
6
+
7
+ DEFAULT_THEMES = %w[
8
+ red orange amber yellow lime green emerald teal cyan sky blue
9
+ indigo violet purple fuchsia pink rose
10
+ slate gray zinc neutral stone taupe mauve mist olive
11
+ ].freeze
12
+
13
+ def initialize
14
+ @themes = DEFAULT_THEMES.dup
15
+ @default = "sky"
16
+ @theme_cookie = "theme"
17
+ @mode_cookie = "mode"
18
+ @cookie_max_age = 60 * 60 * 24 * 365
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,28 @@
1
+ module TailwindThemePicker
2
+
3
+ class Engine < ::Rails::Engine
4
+
5
+ initializer "tailwind_theme_picker.helpers" do
6
+ ActiveSupport.on_load(:action_view) do
7
+ include TailwindThemePicker::ViewHelper
8
+ end
9
+ end
10
+
11
+ # 1. Serve the Stimulus controller as a propshaft asset.
12
+ # 2. Append our importmap.rb so the host doesn't need to pin manually.
13
+ initializer "tailwind_theme_picker.importmap", before: "importmap" do |app|
14
+ if app.config.respond_to?(:importmap)
15
+ app.config.importmap.paths << root.join("config/importmap.rb")
16
+ app.config.importmap.cache_sweepers << root.join("app/javascript")
17
+ end
18
+ end
19
+
20
+ initializer "tailwind_theme_picker.assets" do |app|
21
+ if app.config.respond_to?(:assets)
22
+ app.config.assets.paths << root.join("app/javascript")
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,5 @@
1
+ module TailwindThemePicker
2
+
3
+ VERSION = "0.1.0"
4
+
5
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "tailwind_theme_picker/version"
2
+ require_relative "tailwind_theme_picker/configuration"
3
+ require_relative "tailwind_theme_picker/engine" if defined?(Rails)
4
+
5
+ module TailwindThemePicker
6
+
7
+ class << self
8
+
9
+ attr_writer :configuration
10
+
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def configure
16
+ yield(configuration)
17
+ end
18
+
19
+ def reset_configuration!
20
+ @configuration = Configuration.new
21
+ end
22
+
23
+ end
24
+
25
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tailwind_theme_picker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - 16554289+optimuspwnius@users.noreply.github.com
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: actionview
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '7.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.0'
40
+ description: Floating theme picker for Tailwind-based Rails apps. Ships a Stimulus
41
+ controller, Slim partial, helpers, and a themes stylesheet. Persists choice in two
42
+ cookies (theme, mode) so the server can paint the right classes on <html> before
43
+ any JS runs — no flash of unstyled content on returning visits.
44
+ email:
45
+ - 16554289+optimuspwnius@users.noreply.github.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - LICENSE.txt
51
+ - README.md
52
+ - app/helpers/tailwind_theme_picker/view_helper.rb
53
+ - app/javascript/tailwind_theme_picker/controllers/theme_controller.js
54
+ - app/views/tailwind_theme_picker/_picker.html.slim
55
+ - assets/stylesheets/tailwind_theme_picker/themes.css
56
+ - config/importmap.rb
57
+ - lib/generators/tailwind_theme_picker/install/install_generator.rb
58
+ - lib/tailwind_theme_picker.rb
59
+ - lib/tailwind_theme_picker/configuration.rb
60
+ - lib/tailwind_theme_picker/engine.rb
61
+ - lib/tailwind_theme_picker/version.rb
62
+ homepage: https://github.com/optimuspwnius/tailwind-theme-picker
63
+ licenses:
64
+ - MIT
65
+ metadata:
66
+ homepage_uri: https://github.com/optimuspwnius/tailwind-theme-picker
67
+ source_code_uri: https://github.com/optimuspwnius/tailwind-theme-picker
68
+ changelog_uri: https://github.com/optimuspwnius/tailwind-theme-picker/blob/main/CHANGELOG.md
69
+ allowed_push_host: https://rubygems.org
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '3.2'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 4.0.7
85
+ specification_version: 4
86
+ summary: Drop-in theme + light/dark picker for Rails apps.
87
+ test_files: []