daisyui 2.1.0 → 2.2.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.
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ # The declarative layer over `register_modifiers`: compound looks, defaults,
5
+ # browser states and parts (slots), declared once in the class body.
6
+ #
7
+ # class Widget < DaisyUI::Button
8
+ # variants do
9
+ # base "font-medium"
10
+ # variant :size, sm: "btn-sm [--fontsize:var(--text-sm)]"
11
+ # default :size, :md
12
+ # compound style: :outline, color: nil, class: "[--btn-bg:var(--color-base-100)]"
13
+ # state :hover, "hover"
14
+ # on :hover, style: %i[ghost link], class: "[--btn-bg:transparent]"
15
+ # end
16
+ # end
17
+ #
18
+ # `variant` and `flag` write through `register_modifiers` (so ClassMerge
19
+ # groups, inheritance and finalize! stay as they are); everything else lands
20
+ # in the class's `variant_definitions`, a frozen Hash replaced on every
21
+ # write. `Widget.variants.schema` describes it all as plain data. Matching,
22
+ # validation and rendering live in Variants::Match, Variants::Resolution and
23
+ # Variants::Rendering.
24
+ class Variants
25
+ EMPTY = Shareable.make(
26
+ { base: nil, defaults: {}, compounds: [], rules: [], states: {}, slots: {}, slot_classes: {} }
27
+ )
28
+ STATE_PREFIX = /\A[^\s"'`;{}\\]+\z/
29
+
30
+ # The DSL for `klass`, with `block` evaluated against it.
31
+ def self.build(klass, &block)
32
+ new(klass).tap { |dsl| dsl.instance_exec(&block) if block }
33
+ end
34
+
35
+ def initialize(klass)
36
+ @klass = klass
37
+ end
38
+
39
+ # Classes every instance gets, after the inherited base.
40
+ def base(classes)
41
+ write(base: [definitions[:base], classes].compact.join(" "))
42
+ end
43
+
44
+ # A group of alternatives: `variant :size, sm: "btn-sm", lg: "btn-lg"`. A
45
+ # Hash value gives classes per slot (`sm: { root: "…", menu: "…" }`).
46
+ def variant(group, **members)
47
+ register(group => members.transform_values { |classes| root_classes(classes) })
48
+ members.each { |member, classes| write_slot_classes(member, classes) }
49
+ self
50
+ end
51
+
52
+ # An on/off modifier: `flag :wide, "btn-wide"`, or a flag with no class of
53
+ # its own (`flag :collapsed`) that compounds and `on` rules can match.
54
+ def flag(name, classes = nil)
55
+ return tap { @klass.register_flags(name) } if classes.nil?
56
+
57
+ register(name => root_classes(classes))
58
+ write_slot_classes(name, classes)
59
+ self
60
+ end
61
+
62
+ # The member used when the caller names no member of `group`.
63
+ def default(group, member)
64
+ check.member!(group, member)
65
+ write(defaults: definitions[:defaults].merge(group => member))
66
+ end
67
+
68
+ # Adds `class:` and sets the variants in `set:` (members the caller did
69
+ # not name) when every key of `match` holds. See Variants::Match.
70
+ def compound(**match)
71
+ classes = match.delete(:class)
72
+ set = match.delete(:set) || {}
73
+ raise ArgumentError, "#{owner}: compound #{match.inspect} needs class: or set:" if classes.nil? && set.empty?
74
+
75
+ check.match!(match)
76
+ set.each { |group, member| check.member!(group, member) }
77
+ add(:compounds, match:, class: slot_hash(classes), set:)
78
+ end
79
+
80
+ # Names a Tailwind variant prefix once: `state :open, "[.dropdown[open]>&]"`.
81
+ def state(name, prefix)
82
+ raise ArgumentError, "#{owner}: state #{name.inspect} needs a Tailwind variant, got #{prefix.inspect}" unless STATE_PREFIX.match?(prefix.to_s)
83
+
84
+ write(states: definitions[:states].merge(name => prefix.to_s))
85
+ end
86
+
87
+ # Like compound, with every class prefixed by each of the `states`.
88
+ def on(states, **match)
89
+ states = Array(states)
90
+ classes = match.delete(:class)
91
+ raise ArgumentError, "#{owner}: on #{states.inspect} needs class:" if classes.nil?
92
+
93
+ states.each { |name| check.state!(name) }
94
+ check.match!(match)
95
+ add(:rules, states:, match:, class: slot_hash(classes))
96
+ end
97
+
98
+ # A part of the component (`root` is the component itself), with the
99
+ # classes it always gets. Read with Base#slot_classes / #slot_options.
100
+ def slot(name, classes = nil)
101
+ raise ArgumentError, "#{owner}: :root is the component itself, not a slot to declare" if name == :root
102
+
103
+ write(slots: definitions[:slots].merge(name => classes))
104
+ end
105
+
106
+ # Plain, frozen data describing the component, for docs pages, galleries
107
+ # and design-tool parity checks.
108
+ def schema
109
+ Schema.new(@klass).to_h
110
+ end
111
+
112
+ # Every class the component's declarations can render, prefixed as
113
+ # DaisyUI.configuration.prefix renders them: what Tailwind needs to see.
114
+ def literal_classes(prefix: nil)
115
+ Schema.new(@klass).literal_classes(prefix.to_s)
116
+ end
117
+
118
+ private
119
+
120
+ def definitions = @klass.variant_definitions
121
+
122
+ def owner = @klass.name || @klass.inspect
123
+
124
+ def check = Match.new(@klass)
125
+
126
+ def write(**changes)
127
+ @klass.variant_definitions = definitions.merge(changes)
128
+ self
129
+ end
130
+
131
+ def add(key, **entry)
132
+ write(key => [*definitions[key], entry])
133
+ end
134
+
135
+ def register(modifiers)
136
+ @klass.register_modifiers(modifiers)
137
+ end
138
+
139
+ def root_classes(classes)
140
+ classes.is_a?(Hash) ? slot_hash(classes)[:root].to_s : classes.to_s
141
+ end
142
+
143
+ # Slot parts of a member, kept apart from `modifiers`, which only ever
144
+ # holds the root's String.
145
+ def write_slot_classes(member, classes)
146
+ parts = classes.is_a?(Hash) ? slot_hash(classes).except(:root) : {}
147
+ slot_classes = definitions[:slot_classes].except(member)
148
+ write(slot_classes: parts.empty? ? slot_classes : slot_classes.merge(member => parts))
149
+ end
150
+
151
+ # `"a b"` or `{ root: "a", menu: "b" }` as `{ slot => classes }`.
152
+ def slot_hash(classes)
153
+ return {} if classes.nil?
154
+ return { root: classes.to_s } unless classes.is_a?(Hash)
155
+
156
+ classes.each_key { |slot| check.slot!(slot) }
157
+ classes.transform_values(&:to_s)
158
+ end
159
+ end
160
+ 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.2.0"
5
5
  end
data/lib/daisy_ui.rb CHANGED
@@ -14,6 +14,8 @@ loader.ignore("#{__dir__}/daisy_ui/updated_at.rb")
14
14
  # The Rails engine references Rails::Engine, so it is required explicitly below
15
15
  # (only when Rails is present) rather than autoloaded.
16
16
  loader.ignore("#{__dir__}/daisy_ui/engine.rb")
17
+ # lib/tasks holds the engine's rake tasks, no constants.
18
+ loader.ignore("#{__dir__}/tasks")
17
19
  loader.setup # ready!
18
20
  loader.load_file("#{__dir__}/daisy_ui/base.rb")
19
21
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Loaded by the Rails engine (Rails loads an engine's lib/tasks/*.rake).
4
+ namespace :daisyui do
5
+ desc "Write the Tailwind sources of the app's own DaisyUI components to OUTPUT (stdout without one)"
6
+ task :tailwind, [:output] => :environment do |_task, args|
7
+ argv = ["--app"]
8
+ argv.push("--output", args[:output]) if args[:output]
9
+ status = DaisyUI::Tailwind::Command.run(argv)
10
+ exit(status) unless status.zero?
11
+ end
12
+ 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.2.0
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
@@ -125,6 +128,7 @@ files:
125
128
  - lib/daisy_ui/loading.rb
126
129
  - lib/daisy_ui/mask.rb
127
130
  - lib/daisy_ui/mcp_server.rb
131
+ - lib/daisy_ui/mcp_server/variants_guide.rb
128
132
  - lib/daisy_ui/megamenu.rb
129
133
  - lib/daisy_ui/menu.rb
130
134
  - lib/daisy_ui/menu_item.rb
@@ -157,6 +161,16 @@ files:
157
161
  - lib/daisy_ui/table.rb
158
162
  - lib/daisy_ui/table_row.rb
159
163
  - lib/daisy_ui/tabs.rb
164
+ - lib/daisy_ui/tailwind.rb
165
+ - lib/daisy_ui/tailwind/app_sources.rb
166
+ - lib/daisy_ui/tailwind/candidate.rb
167
+ - lib/daisy_ui/tailwind/command.rb
168
+ - lib/daisy_ui/tailwind/lexer.rb
169
+ - lib/daisy_ui/tailwind/manifest.rb
170
+ - lib/daisy_ui/tailwind/manifest_builder.rb
171
+ - lib/daisy_ui/tailwind/sources.rb
172
+ - lib/daisy_ui/tailwind/unknown_component_error.rb
173
+ - lib/daisy_ui/tailwind/usage.rb
160
174
  - lib/daisy_ui/text_rotate.rb
161
175
  - lib/daisy_ui/textarea.rb
162
176
  - lib/daisy_ui/theme_controller.rb
@@ -167,8 +181,14 @@ files:
167
181
  - lib/daisy_ui/unknown_modifier_error.rb
168
182
  - lib/daisy_ui/updated_at.rb
169
183
  - lib/daisy_ui/validator.rb
184
+ - lib/daisy_ui/variants.rb
185
+ - lib/daisy_ui/variants/match.rb
186
+ - lib/daisy_ui/variants/rendering.rb
187
+ - lib/daisy_ui/variants/resolution.rb
188
+ - lib/daisy_ui/variants/schema.rb
170
189
  - lib/daisy_ui/version.rb
171
190
  - lib/daisyui.rb
191
+ - lib/tasks/daisyui.rake
172
192
  homepage: https://github.com/mhenrixon/daisyui
173
193
  licenses:
174
194
  - MIT