daisyui 2.1.0 → 2.1.1

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.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "daisyui"
5
+
6
+ exit DaisyUI::Tailwind::Command.run(ARGV)
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module Tailwind
5
+ # Which words can go into Tailwind's `@source inline("…")`. inline() is a
6
+ # quoted string with brace expansion, and one unbalanced bracket in it
7
+ # makes Tailwind drop every class after it, so a candidate starts and ends
8
+ # like a utility or variant, has balanced () and [], and holds no quote,
9
+ # brace, semicolon, backslash or backtick. A `/` only introduces a
10
+ # fraction or an opacity (`w-1/2`, `bg-base-100/50`), never a path.
11
+ #
12
+ # @api private
13
+ module Candidate
14
+ SHAPE = /\A[a-z@!\[*-][^\s"'`;{}\\]*[a-z0-9\])%!]\z/
15
+ PATH = %r{/(?![0-9\[])}
16
+ CLOSING = { ")" => "(", "]" => "[" }.freeze
17
+
18
+ class << self
19
+ def valid?(token)
20
+ SHAPE.match?(token) && token.match?(/[a-z]/) && !PATH.match?(token) && balanced?(token)
21
+ end
22
+
23
+ # The candidates among the whitespace-separated words of `classes`.
24
+ def tokens(classes)
25
+ classes.split.select { |token| valid?(token) }
26
+ end
27
+
28
+ private
29
+
30
+ def balanced?(token)
31
+ open = []
32
+ token.each_char do |char|
33
+ if CLOSING.value?(char)
34
+ open << char
35
+ elsif CLOSING.key?(char)
36
+ return false unless open.pop == CLOSING[char]
37
+ end
38
+ end
39
+ open.empty?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module Tailwind
5
+ # `daisyui-tailwind`: the Tailwind sources for the components an app uses,
6
+ # for its CSS build to call (`bundle exec daisyui-tailwind app -o …`).
7
+ # Returns the exit status; usage errors print to `err` and return 1.
8
+ class Command
9
+ BANNER = <<~TEXT
10
+ Usage: daisyui-tailwind [options] [PATH...]
11
+
12
+ Prints Tailwind CSS v4 sources (@source inline) for the DaisyUI components
13
+ that the Ruby and templates under each PATH use, plus the components those
14
+ render. @import the output from your Tailwind entry.
15
+
16
+ TEXT
17
+
18
+ def self.run(argv, out: $stdout, err: $stderr) = new(out:, err:).run(argv)
19
+
20
+ def initialize(out:, err:)
21
+ @out = out
22
+ @err = err
23
+ end
24
+
25
+ def run(argv)
26
+ require "optparse"
27
+
28
+ options = { components: [], format: "css", responsive: [] }
29
+ parser = parser(options)
30
+ paths = parser.parse(argv)
31
+ return help(parser) if options[:help]
32
+ return usage(parser) if paths.empty? && options[:components].empty?
33
+
34
+ result = Tailwind.sources(scan: paths, **options.slice(:components, :format, :responsive, :prefix))
35
+ options[:output] ? write(options[:output], result) : @out.write(result)
36
+ 0
37
+ rescue OptionParser::ParseError, ArgumentError, SystemCallError => e
38
+ @err.puts "daisyui-tailwind: #{e.message}"
39
+ 1
40
+ end
41
+
42
+ private
43
+
44
+ def parser(options)
45
+ OptionParser.new do |opts|
46
+ opts.banner = BANNER
47
+ opts.on("-c", "--component NAMES", Array, "Components to include, e.g. Button,Navbar (repeatable)") do |names|
48
+ options[:components].concat(names)
49
+ end
50
+ opts.on("-f", "--format FORMAT", %w[css json], "css (default) or json") { |format| options[:format] = format }
51
+ opts.on("-o", "--output FILE", "Write to FILE, only when its content changes") { |file| options[:output] = file }
52
+ opts.on("--responsive BREAKPOINTS", Array, "Add breakpoint variants, e.g. sm,md,lg") do |breakpoints|
53
+ options[:responsive].concat(breakpoints)
54
+ end
55
+ opts.on("--prefix PREFIX", "The DaisyUI.configuration.prefix your app sets") { |prefix| options[:prefix] = prefix }
56
+ opts.on("-h", "--help", "Show this help") { options[:help] = true }
57
+ end
58
+ end
59
+
60
+ def help(parser)
61
+ @out.puts parser
62
+ 0
63
+ end
64
+
65
+ def usage(parser)
66
+ @err.puts parser
67
+ 1
68
+ end
69
+
70
+ # Only on change: a CSS watcher that sees its own input rewritten on
71
+ # every build rebuilds forever.
72
+ def write(path, content)
73
+ if File.exist?(path) && File.read(path) == content
74
+ @err.puts "daisyui-tailwind: #{path} is up to date"
75
+ else
76
+ require "fileutils"
77
+
78
+ FileUtils.mkdir_p(File.dirname(path))
79
+ File.write(path, content)
80
+ @err.puts "daisyui-tailwind: wrote #{path}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module Tailwind
5
+ # Reads a component's Ruby the way the manifest needs it: the words of its
6
+ # string literals (where its sub-part classes live, e.g. "card-body") and
7
+ # the constants its code references (the components it renders). Ripper
8
+ # keeps comments out of both.
9
+ #
10
+ # @api private
11
+ module Lexer
12
+ class << self
13
+ def strings(source)
14
+ tokens(source, :on_tstring_content).flat_map(&:split)
15
+ end
16
+
17
+ def constants(source)
18
+ tokens(source, :on_const).uniq
19
+ end
20
+
21
+ private
22
+
23
+ def tokens(source, type)
24
+ require "ripper"
25
+
26
+ ::Ripper.lex(source).filter_map { |(_position, kind, token)| token if kind == type }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module Tailwind
5
+ # The committed tailwind_manifest.json: per component (by short name, "Button"),
6
+ # its class, daisyUI name, files, the components it renders and its
7
+ # classes; `shared` is base.rb, which every component draws on. The JSON
8
+ # is the contract for build scripts in other languages, so its keys are
9
+ # documented in the README.
10
+ class Manifest
11
+ def self.load(path = MANIFEST_PATH, root: ROOT)
12
+ require "json"
13
+
14
+ new(JSON.parse(File.read(path)), root:)
15
+ end
16
+
17
+ attr_reader :components, :shared, :root
18
+
19
+ def initialize(data, root: ROOT)
20
+ @components = data.fetch("components")
21
+ @shared = data.fetch("shared")
22
+ @root = root
23
+ end
24
+
25
+ def component_names = components.keys
26
+
27
+ # `names` ("Button" or "DaisyUI::Button") plus every component they
28
+ # render, transitively. Sorted.
29
+ def resolve(names)
30
+ resolved = []
31
+ pending = names.map { |name| canonical(name) }
32
+ until pending.empty?
33
+ name = pending.pop
34
+ next if resolved.include?(name)
35
+
36
+ resolved << name
37
+ pending.concat(components.fetch(name)["depends_on"])
38
+ end
39
+ resolved.sort
40
+ end
41
+
42
+ # Absolute paths of the components' files, for a literal `@source`.
43
+ def files(names) = names.flat_map { |name| absolute(components.fetch(name)["files"]) }
44
+
45
+ def shared_files = absolute(shared["files"])
46
+
47
+ private
48
+
49
+ def canonical(name)
50
+ short = name.to_s.delete_prefix("DaisyUI::")
51
+ return short if components.key?(short)
52
+
53
+ raise UnknownComponentError.new(name, component_names)
54
+ end
55
+
56
+ def absolute(paths) = paths.map { |path| File.join(root, path) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module Tailwind
5
+ # Builds data/daisyui/tailwind_manifest.json from the components
6
+ # themselves (`bundle exec rake tailwind:manifest`); the manifest spec
7
+ # fails while the committed file differs. Per component:
8
+ #
9
+ # - `classes`: its component class (a Symbol Tailwind cannot see), its
10
+ # registered modifiers and the class words in its Ruby's strings
11
+ # - `modifiers`: the registered modifier classes, which `responsive:`
12
+ # can prefix with a breakpoint
13
+ # - `depends_on`: the other components its code references
14
+ #
15
+ # @api private
16
+ module ManifestBuilder
17
+ SCHEMA = 1
18
+ SHARED_FILE = "lib/daisy_ui/base.rb"
19
+
20
+ class << self
21
+ def build(root: ROOT)
22
+ components = gem_components(File.join(root, "lib", "daisy_ui"))
23
+ names = components.map(&:first)
24
+ {
25
+ "schema" => SCHEMA,
26
+ "shared" => shared(root),
27
+ "components" => components.to_h { |name, klass, file| [name, entry(klass, file:, names:, root:)] }
28
+ }
29
+ end
30
+
31
+ def generate(root: ROOT)
32
+ require "json"
33
+
34
+ "#{JSON.pretty_generate(build(root:))}\n"
35
+ end
36
+
37
+ def write(path = MANIFEST_PATH, root: ROOT)
38
+ json = generate(root:)
39
+ File.write(path, json) unless File.exist?(path) && File.read(path) == json
40
+ path
41
+ end
42
+
43
+ def entry(klass, file:, names:, root:)
44
+ source = File.read(file)
45
+ name = klass.component_class&.to_s
46
+ modifiers = modifier_classes(klass)
47
+ {
48
+ "class" => klass.name,
49
+ "name" => name,
50
+ "files" => [file.delete_prefix("#{root}/")],
51
+ "depends_on" => ((Lexer.constants(source) & names) - [short_name(klass)]).sort,
52
+ "modifiers" => modifiers,
53
+ "classes" => ([name, *modifiers].compact | Candidate.tokens(Lexer.strings(source).join(" "))).sort
54
+ }
55
+ end
56
+
57
+ private
58
+
59
+ # The gem's own components, by short name: a DaisyUI:: class defined
60
+ # in a file under lib/daisy_ui (not one a spec or an app defined).
61
+ def gem_components(lib)
62
+ ClassMerge::DaisyGroups.gem_components.filter_map do |klass|
63
+ file = Object.const_source_location(klass.name)&.first
64
+ [short_name(klass), klass, file] if file&.start_with?("#{lib}/")
65
+ end.sort_by(&:first)
66
+ end
67
+
68
+ def shared(root)
69
+ source = File.read(File.join(root, SHARED_FILE))
70
+ { "files" => [SHARED_FILE], "classes" => Candidate.tokens(Lexer.strings(source).join(" ")).uniq.sort }
71
+ end
72
+
73
+ def modifier_classes(klass)
74
+ classes = (klass.modifiers || {}).values.flat_map { |value| value.to_s.split }.uniq.sort
75
+ invalid = classes.reject { |token| Candidate.valid?(token) }
76
+ return classes if invalid.empty?
77
+
78
+ raise ArgumentError, "#{klass.name} registers #{invalid.map(&:inspect).join(', ')}, " \
79
+ "which Tailwind cannot read from @source inline(…)"
80
+ end
81
+
82
+ def short_name(klass) = klass.name.split("::").last
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module Tailwind
5
+ # Renders the Tailwind sources for a set of (resolved) components:
6
+ #
7
+ # - `to_css`: a partial for the app's Tailwind entry to @import, one
8
+ # `@source inline("…")` per component after base.rb's shared classes. It
9
+ # names no path, so it is the same on every machine.
10
+ # - `to_json`: the same for a build script, plus each component's absolute
11
+ # files for anyone who wants a literal `@source` instead.
12
+ #
13
+ # `responsive:` adds `<breakpoint>:` variants of the classes `responsive:`
14
+ # renders (a component's own class and its modifiers). `prefix:` mirrors
15
+ # DaisyUI.configuration.prefix: the component class and modifiers always
16
+ # render prefixed, while a class string in a component's Ruby may render
17
+ # either way (`component_classes("card-body")` is prefixed, Tooltip's
18
+ # "after:hidden" is not), so those are given both ways.
19
+ class Sources
20
+ BREAKPOINT = /\A@?[a-z0-9][a-z0-9-]*\z/
21
+ PREFIX = /\A[^\s"'`;{}\\]+\z/
22
+
23
+ attr_reader :components
24
+
25
+ def initialize(manifest, components, responsive: [], prefix: nil)
26
+ @manifest = manifest
27
+ @components = components
28
+ @breakpoints = responsive.map(&:to_s)
29
+ @prefix = prefix.to_s
30
+ validate!
31
+ end
32
+
33
+ def render(format)
34
+ case format.to_s
35
+ when "css" then to_css
36
+ when "json" then to_json
37
+ else raise ArgumentError, "unknown format #{format.inspect}: css or json"
38
+ end
39
+ end
40
+
41
+ def to_css
42
+ header = "/* Generated by daisyui-tailwind (daisyui #{VERSION}): do not edit. */\n"
43
+ return "#{header}/* No DaisyUI components referenced. */\n" if components.empty?
44
+
45
+ lines = [
46
+ "/* daisyUI components: #{components.join(', ')} */",
47
+ "/* Shared by every component (lib/daisy_ui/base.rb) */",
48
+ inline(shared_classes)
49
+ ]
50
+ components.each { |name| lines.push("/* #{@manifest.components.fetch(name)['class']} */", inline(classes(name))) }
51
+ "#{header}#{lines.join("\n")}\n"
52
+ end
53
+
54
+ def to_json(*)
55
+ require "json"
56
+
57
+ "#{JSON.pretty_generate(json)}\n"
58
+ end
59
+
60
+ private
61
+
62
+ def json
63
+ files = @manifest.shared_files + @manifest.files(components)
64
+ {
65
+ "daisyui" => VERSION,
66
+ "components" => components.to_h { |name| [name, component_json(name)] },
67
+ "shared" => { "files" => @manifest.shared_files, "classes" => shared_classes },
68
+ "classes" => (shared_classes + components.flat_map { |name| classes(name) }).uniq.sort,
69
+ "files" => files.uniq.sort
70
+ }
71
+ end
72
+
73
+ def component_json(name)
74
+ entry = @manifest.components.fetch(name)
75
+ entry.slice("class", "name", "depends_on").merge("files" => @manifest.files([name]), "classes" => classes(name))
76
+ end
77
+
78
+ def classes(name)
79
+ entry = @manifest.components.fetch(name)
80
+ expand(entry["classes"], [entry["name"], *entry["modifiers"]].compact)
81
+ end
82
+
83
+ def shared_classes = expand(@manifest.shared["classes"], @manifest.shared["classes"])
84
+
85
+ def expand(classes, responsive)
86
+ prefixed = classes.map { |token| "#{@prefix}#{token}" }
87
+ prefixed += classes - responsive unless @prefix.empty?
88
+ prefixed + @breakpoints.flat_map { |breakpoint| responsive.map { |token| "#{breakpoint}:#{@prefix}#{token}" } }
89
+ end
90
+
91
+ def inline(classes) = %(@source inline("#{classes.join(' ')}");)
92
+
93
+ def validate!
94
+ @breakpoints.each do |breakpoint|
95
+ raise ArgumentError, "invalid breakpoint #{breakpoint.inspect}: give the variant name, e.g. sm or @md" unless BREAKPOINT.match?(breakpoint)
96
+ end
97
+ return if @prefix.empty? || PREFIX.match?(@prefix)
98
+
99
+ raise ArgumentError, "invalid prefix #{@prefix.inspect}"
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "did_you_mean"
4
+
5
+ module DaisyUI
6
+ module Tailwind
7
+ # A component name (`--component Buton`) the manifest does not have.
8
+ class UnknownComponentError < ArgumentError
9
+ def initialize(name, known)
10
+ suggestions = DidYouMean::SpellChecker.new(dictionary: known).correct(name.to_s)
11
+ message = "DaisyUI has no component #{name}"
12
+ message = "#{message}. Did you mean? #{suggestions.join(', ')}" unless suggestions.empty?
13
+ super(message)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module Tailwind
5
+ # Finds the components an app's Ruby and templates reference:
6
+ #
7
+ # - the constant: `DaisyUI::Card`, `class AppNavbar < DaisyUI::Navbar`
8
+ # - an instance: `Card.new(…)`
9
+ # - a kit call, with parentheses (`Button(:primary)`) or without
10
+ # (`Badge :primary do`, `Hero do`, `Modal { … }`, `Toast class: "…"`,
11
+ # `Button variant do`)
12
+ #
13
+ # Comments are skipped (each file's own kind: `#` lines in Ruby, `<%# %>`
14
+ # in ERB, `-#` in HAML, `/` in Slim), so "# Loading state…" is not a use
15
+ # of Loading, while a HAML `#sidebar= …` line still counts. A name is matched whole: `MyButton(` and `UI::Card(` are not
16
+ # uses (an app wrapper `UI::Card < DaisyUI::Card` is found by its own
17
+ # file). Matching on text over-reports rather than misses: a false hit
18
+ # costs some CSS, a miss leaves a component unstyled.
19
+ class Usage
20
+ COMMENTS = {
21
+ "rb" => /^[ \t]*#.*$/,
22
+ "erb" => /<%#.*?%>/m,
23
+ "haml" => /^[ \t]*-#.*$/,
24
+ "slim" => %r{^[ \t]*/.*$}
25
+ }.freeze
26
+ EXTENSIONS = COMMENTS.keys.freeze
27
+ # What follows a name in a call: `(`, `.new`, a block, or arguments
28
+ # without parentheses: `:primary`, `class: …`, `"…"`, a splat
29
+ # (`*modifiers`, `**options`, `&block`), or a variable that ends the line
30
+ # or is followed by `,`, `do` or `{` (`Button variant do`), which keeps
31
+ # "Join thousands of…" in a string from counting.
32
+ CALL = /
33
+ \( | \.new\b | [ \t]*\{ |
34
+ [ \t]+ (?: do\b | :[a-z_] | ["'] | [a-z_]\w*:[ \t] | (?: \*\*? | & ) [a-z_@(] |
35
+ [a-z_@][\w@]* (?: \.[a-z_]\w* )* [ \t]* (?: , | do\b | \{ | $ ) )
36
+ /x
37
+
38
+ def initialize(names)
39
+ names = names.sort_by { |name| -name.length }.map { |name| Regexp.escape(name) }.join("|")
40
+ @pattern = /DaisyUI::(#{names})\b|(?<![A-Za-z0-9_:])(#{names})(?=#{CALL})/
41
+ end
42
+
43
+ # Sorted names referenced in the files under `paths` (directories or
44
+ # files). A path that does not exist raises: a typo must not quietly
45
+ # leave every component out.
46
+ def scan(paths)
47
+ files(paths).flat_map { |file| references(File.read(file).scrub, kind: File.extname(file).delete_prefix(".")) }
48
+ .uniq.sort
49
+ end
50
+
51
+ # Sorted names referenced in `source`, a file of `kind` (its extension).
52
+ def references(source, kind: "rb")
53
+ source.gsub(COMMENTS.fetch(kind, COMMENTS["rb"]), "").scan(@pattern).flat_map(&:compact).uniq.sort
54
+ end
55
+
56
+ private
57
+
58
+ def files(paths)
59
+ paths.flat_map do |path|
60
+ path = path.to_s
61
+ if File.directory?(path)
62
+ Dir.glob(File.join(path, "**", "*.{#{EXTENSIONS.join(',')}}"))
63
+ elsif File.file?(path)
64
+ [path]
65
+ else
66
+ raise ArgumentError, "no such file or directory: #{path}"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ # Tailwind CSS sources for only the daisyUI components an app renders.
5
+ #
6
+ # Pointing Tailwind's `@source` at the whole gem hands it every class of
7
+ # every component (and ClassMerge's catalogue of Tailwind utilities), yet
8
+ # still misses what Tailwind v4 cannot see in Ruby: a Symbol component class
9
+ # (`self.component_class = :btn`) and anything in a comment. The gem's
10
+ # manifest (data/daisyui/tailwind_manifest.json, built from the components by
11
+ # `rake tailwind:manifest`) lists each component's classes and the
12
+ # components it renders, so an app's CSS build gets `@source inline(…)` for
13
+ # just the components it uses:
14
+ #
15
+ # DaisyUI::Tailwind.sources(scan: ["app/views", "app/components"])
16
+ # DaisyUI::Tailwind.sources(components: %w[Button Navbar], format: :json)
17
+ #
18
+ # `bundle exec daisyui-tailwind` (DaisyUI::Tailwind::Command) does the same
19
+ # from a build script.
20
+ module Tailwind
21
+ ROOT = File.expand_path("../..", __dir__).freeze
22
+ # In the gem's data/ dir, not lib/: an app that still points Tailwind at
23
+ # the gem's lib/ must not start scanning it.
24
+ MANIFEST_PATH = File.join(ROOT, "data", "daisyui", "tailwind_manifest.json").freeze
25
+
26
+ class << self
27
+ def manifest = Manifest.load
28
+
29
+ # The components the Ruby and templates under `scan` reference, plus the
30
+ # `components` named, plus every component those render. Sorted names.
31
+ def used_components(scan: [], components: [], manifest: self.manifest)
32
+ found = scan.empty? ? [] : Usage.new(manifest.component_names).scan(scan)
33
+ manifest.resolve(found + components)
34
+ end
35
+
36
+ # CSS (`format: :css`, for Tailwind to @import) or JSON for those
37
+ # components. `responsive:` adds breakpoint variants (`%w[sm md]`) of the
38
+ # classes `responsive:` renders; `prefix:` defaults to the
39
+ # DaisyUI.configuration.prefix the components render with.
40
+ def sources(scan: [], components: [], format: :css, responsive: [], prefix: DaisyUI.configuration.prefix)
41
+ manifest = self.manifest
42
+ used = used_components(scan:, components:, manifest:)
43
+ Sources.new(manifest, used, responsive:, prefix:).render(format)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -3,5 +3,5 @@
3
3
  module DaisyUI
4
4
  # This timestamp is automatically updated when releasing a new version
5
5
  # Format: YYYY-MM-DD HH:MM:SS UTC
6
- UPDATED_AT = "2026-10-15 20:45:37 UTC"
6
+ UPDATED_AT = "2026-10-17 07:27:19 UTC"
7
7
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DaisyUI
4
- VERSION = "2.1.0"
4
+ VERSION = "2.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daisyui
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -55,6 +55,7 @@ description: DaisyUI is a comprehensive Ruby UI component library that brings Da
55
55
  email: mikael@zoolutions.llc
56
56
  executables:
57
57
  - daisyui-mcp
58
+ - daisyui-tailwind
58
59
  extensions: []
59
60
  extra_rdoc_files: []
60
61
  files:
@@ -69,7 +70,9 @@ files:
69
70
  - app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js
70
71
  - app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js.map
71
72
  - config/importmap.rb
73
+ - data/daisyui/tailwind_manifest.json
72
74
  - exe/daisyui-mcp
75
+ - exe/daisyui-tailwind
73
76
  - lib/daisy_ui.rb
74
77
  - lib/daisy_ui/accordion.rb
75
78
  - lib/daisy_ui/alert.rb
@@ -157,6 +160,15 @@ files:
157
160
  - lib/daisy_ui/table.rb
158
161
  - lib/daisy_ui/table_row.rb
159
162
  - lib/daisy_ui/tabs.rb
163
+ - lib/daisy_ui/tailwind.rb
164
+ - lib/daisy_ui/tailwind/candidate.rb
165
+ - lib/daisy_ui/tailwind/command.rb
166
+ - lib/daisy_ui/tailwind/lexer.rb
167
+ - lib/daisy_ui/tailwind/manifest.rb
168
+ - lib/daisy_ui/tailwind/manifest_builder.rb
169
+ - lib/daisy_ui/tailwind/sources.rb
170
+ - lib/daisy_ui/tailwind/unknown_component_error.rb
171
+ - lib/daisy_ui/tailwind/usage.rb
160
172
  - lib/daisy_ui/text_rotate.rb
161
173
  - lib/daisy_ui/textarea.rb
162
174
  - lib/daisy_ui/theme_controller.rb