daisy_components 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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.cursor/rules/how-to-write-tests.mdc +67 -0
  3. data/.cursor/rules/preview-file-stucture.mdc +95 -0
  4. data/.cursorrules +64 -0
  5. data/.github/dependabot.yml +12 -0
  6. data/.github/workflows/ci.yml +65 -0
  7. data/.gitignore +13 -0
  8. data/.vscode/launch.json +55 -0
  9. data/.vscode/settings.json +44 -0
  10. data/CHANGELOG.md +39 -0
  11. data/Gemfile +32 -0
  12. data/Gemfile.lock +335 -0
  13. data/MIT-LICENSE +20 -0
  14. data/README.md +64 -0
  15. data/Rakefile +7 -0
  16. data/app/assets/images/daisy_components/.keep +0 -0
  17. data/app/assets/stylesheets/daisy_ui/application.css +15 -0
  18. data/app/components/daisy_ui/actions/button.rb +262 -0
  19. data/app/components/daisy_ui/actions/dropdown.rb +125 -0
  20. data/app/components/daisy_ui/actions/swap.rb +126 -0
  21. data/app/components/daisy_ui/base_component.rb +29 -0
  22. data/app/components/daisy_ui/data_display/accordion.rb +128 -0
  23. data/app/components/daisy_ui/data_display/accordion_item.rb +121 -0
  24. data/app/components/daisy_ui/data_display/avatar.rb +131 -0
  25. data/app/components/daisy_ui/data_display/avatar_group.rb +102 -0
  26. data/app/components/daisy_ui/data_display/badge.rb +126 -0
  27. data/app/components/daisy_ui/data_display/card/actions.rb +94 -0
  28. data/app/components/daisy_ui/data_display/card/body.rb +113 -0
  29. data/app/components/daisy_ui/data_display/card/figure.rb +44 -0
  30. data/app/components/daisy_ui/data_display/card/title.rb +56 -0
  31. data/app/components/daisy_ui/data_display/card.rb +157 -0
  32. data/app/components/daisy_ui/data_display/chat.rb +92 -0
  33. data/app/components/daisy_ui/data_display/chat_bubble/metadata.rb +71 -0
  34. data/app/components/daisy_ui/data_display/chat_bubble.rb +166 -0
  35. data/app/components/daisy_ui/divider.rb +9 -0
  36. data/app/components/daisy_ui/item.rb +20 -0
  37. data/app/components/daisy_ui/title.rb +9 -0
  38. data/app/controllers/concerns/.keep +0 -0
  39. data/app/controllers/daisy_ui/application_controller.rb +6 -0
  40. data/app/helpers/daisy_ui/application_helper.rb +6 -0
  41. data/app/helpers/daisy_ui/icons_helper.rb +296 -0
  42. data/app/views/layouts/daisyui/application.html.erb +17 -0
  43. data/bin/parse_coverage.rb +59 -0
  44. data/bin/rails +57 -0
  45. data/bin/rubocop +10 -0
  46. data/bin/scrape_component +86 -0
  47. data/daisy_components.gemspec +33 -0
  48. data/docs/assets/2025-01_screeshot_1.png +0 -0
  49. data/docs/assets/2025-01_screeshot_2.png +0 -0
  50. data/docs/assets/2025-01_screeshot_3.png +0 -0
  51. data/lib/daisy_components.rb +5 -0
  52. data/lib/daisy_ui/engine.rb +51 -0
  53. data/lib/daisy_ui/version.rb +5 -0
  54. data/lib/daisy_ui.rb +13 -0
  55. data/lib/tasks/daisy_ui_tasks.rake +6 -0
  56. metadata +112 -0
data/Gemfile.lock ADDED
@@ -0,0 +1,335 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ daisy_components (0.1.0)
5
+ rails (>= 8.0.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actioncable (8.0.2)
11
+ actionpack (= 8.0.2)
12
+ activesupport (= 8.0.2)
13
+ nio4r (~> 2.0)
14
+ websocket-driver (>= 0.6.1)
15
+ zeitwerk (~> 2.6)
16
+ actionmailbox (8.0.2)
17
+ actionpack (= 8.0.2)
18
+ activejob (= 8.0.2)
19
+ activerecord (= 8.0.2)
20
+ activestorage (= 8.0.2)
21
+ activesupport (= 8.0.2)
22
+ mail (>= 2.8.0)
23
+ actionmailer (8.0.2)
24
+ actionpack (= 8.0.2)
25
+ actionview (= 8.0.2)
26
+ activejob (= 8.0.2)
27
+ activesupport (= 8.0.2)
28
+ mail (>= 2.8.0)
29
+ rails-dom-testing (~> 2.2)
30
+ actionpack (8.0.2)
31
+ actionview (= 8.0.2)
32
+ activesupport (= 8.0.2)
33
+ nokogiri (>= 1.8.5)
34
+ rack (>= 2.2.4)
35
+ rack-session (>= 1.0.1)
36
+ rack-test (>= 0.6.3)
37
+ rails-dom-testing (~> 2.2)
38
+ rails-html-sanitizer (~> 1.6)
39
+ useragent (~> 0.16)
40
+ actiontext (8.0.2)
41
+ actionpack (= 8.0.2)
42
+ activerecord (= 8.0.2)
43
+ activestorage (= 8.0.2)
44
+ activesupport (= 8.0.2)
45
+ globalid (>= 0.6.0)
46
+ nokogiri (>= 1.8.5)
47
+ actionview (8.0.2)
48
+ activesupport (= 8.0.2)
49
+ builder (~> 3.1)
50
+ erubi (~> 1.11)
51
+ rails-dom-testing (~> 2.2)
52
+ rails-html-sanitizer (~> 1.6)
53
+ activejob (8.0.2)
54
+ activesupport (= 8.0.2)
55
+ globalid (>= 0.3.6)
56
+ activemodel (8.0.2)
57
+ activesupport (= 8.0.2)
58
+ activerecord (8.0.2)
59
+ activemodel (= 8.0.2)
60
+ activesupport (= 8.0.2)
61
+ timeout (>= 0.4.0)
62
+ activestorage (8.0.2)
63
+ actionpack (= 8.0.2)
64
+ activejob (= 8.0.2)
65
+ activerecord (= 8.0.2)
66
+ activesupport (= 8.0.2)
67
+ marcel (~> 1.0)
68
+ activesupport (8.0.2)
69
+ base64
70
+ benchmark (>= 0.3)
71
+ bigdecimal
72
+ concurrent-ruby (~> 1.0, >= 1.3.1)
73
+ connection_pool (>= 2.2.5)
74
+ drb
75
+ i18n (>= 1.6, < 2)
76
+ logger (>= 1.4.2)
77
+ minitest (>= 5.1)
78
+ securerandom (>= 0.3)
79
+ tzinfo (~> 2.0, >= 2.0.5)
80
+ uri (>= 0.13.1)
81
+ addressable (2.8.7)
82
+ public_suffix (>= 2.0.2, < 7.0)
83
+ ast (2.4.3)
84
+ base64 (0.2.0)
85
+ benchmark (0.4.0)
86
+ bigdecimal (3.1.9)
87
+ builder (3.3.0)
88
+ capybara (3.40.0)
89
+ addressable
90
+ matrix
91
+ mini_mime (>= 0.1.3)
92
+ nokogiri (~> 1.11)
93
+ rack (>= 1.6.0)
94
+ rack-test (>= 0.6.3)
95
+ regexp_parser (>= 1.5, < 3.0)
96
+ xpath (~> 3.2)
97
+ concurrent-ruby (1.3.5)
98
+ connection_pool (2.5.3)
99
+ crass (1.0.6)
100
+ css_parser (1.21.1)
101
+ addressable
102
+ date (3.4.1)
103
+ debug (1.10.0)
104
+ irb (~> 1.10)
105
+ reline (>= 0.3.8)
106
+ docile (1.4.1)
107
+ drb (2.2.3)
108
+ erb (5.0.1)
109
+ erubi (1.13.1)
110
+ globalid (1.2.1)
111
+ activesupport (>= 6.1)
112
+ htmlbeautifier (1.4.3)
113
+ htmlentities (4.3.4)
114
+ i18n (1.14.7)
115
+ concurrent-ruby (~> 1.0)
116
+ io-console (0.8.0)
117
+ irb (1.15.2)
118
+ pp (>= 0.6.0)
119
+ rdoc (>= 4.0.0)
120
+ reline (>= 0.4.2)
121
+ json (2.12.2)
122
+ language_server-protocol (3.17.0.5)
123
+ logger (1.7.0)
124
+ loofah (2.24.1)
125
+ crass (~> 1.0.2)
126
+ nokogiri (>= 1.12.0)
127
+ lookbook (2.3.4)
128
+ activemodel
129
+ css_parser
130
+ htmlbeautifier (~> 1.3)
131
+ htmlentities (~> 4.3.4)
132
+ marcel (~> 1.0)
133
+ railties (>= 5.0)
134
+ redcarpet (~> 3.5)
135
+ rouge (>= 3.26, < 5.0)
136
+ view_component (>= 2.0)
137
+ yard (~> 0.9)
138
+ zeitwerk (~> 2.5)
139
+ mail (2.8.1)
140
+ mini_mime (>= 0.1.1)
141
+ net-imap
142
+ net-pop
143
+ net-smtp
144
+ marcel (1.0.4)
145
+ matrix (0.4.2)
146
+ method_source (1.1.0)
147
+ mini_mime (1.1.5)
148
+ minitest (5.25.5)
149
+ net-imap (0.5.8)
150
+ date
151
+ net-protocol
152
+ net-pop (0.1.2)
153
+ net-protocol
154
+ net-protocol (0.2.2)
155
+ timeout
156
+ net-smtp (0.5.1)
157
+ net-protocol
158
+ nio4r (2.7.4)
159
+ nokogiri (1.18.8-aarch64-linux-gnu)
160
+ racc (~> 1.4)
161
+ nokogiri (1.18.8-aarch64-linux-musl)
162
+ racc (~> 1.4)
163
+ nokogiri (1.18.8-arm-linux-gnu)
164
+ racc (~> 1.4)
165
+ nokogiri (1.18.8-arm-linux-musl)
166
+ racc (~> 1.4)
167
+ nokogiri (1.18.8-arm64-darwin)
168
+ racc (~> 1.4)
169
+ nokogiri (1.18.8-x86_64-darwin)
170
+ racc (~> 1.4)
171
+ nokogiri (1.18.8-x86_64-linux-gnu)
172
+ racc (~> 1.4)
173
+ nokogiri (1.18.8-x86_64-linux-musl)
174
+ racc (~> 1.4)
175
+ parallel (1.27.0)
176
+ parser (3.3.8.0)
177
+ ast (~> 2.4.1)
178
+ racc
179
+ pp (0.6.2)
180
+ prettyprint
181
+ prettyprint (0.2.0)
182
+ prism (1.4.0)
183
+ psych (5.2.6)
184
+ date
185
+ stringio
186
+ public_suffix (6.0.2)
187
+ puma (6.6.0)
188
+ nio4r (~> 2.0)
189
+ racc (1.8.1)
190
+ rack (3.1.15)
191
+ rack-session (2.1.1)
192
+ base64 (>= 0.1.0)
193
+ rack (>= 3.0.0)
194
+ rack-test (2.2.0)
195
+ rack (>= 1.3)
196
+ rackup (2.2.1)
197
+ rack (>= 3)
198
+ rails (8.0.2)
199
+ actioncable (= 8.0.2)
200
+ actionmailbox (= 8.0.2)
201
+ actionmailer (= 8.0.2)
202
+ actionpack (= 8.0.2)
203
+ actiontext (= 8.0.2)
204
+ actionview (= 8.0.2)
205
+ activejob (= 8.0.2)
206
+ activemodel (= 8.0.2)
207
+ activerecord (= 8.0.2)
208
+ activestorage (= 8.0.2)
209
+ activesupport (= 8.0.2)
210
+ bundler (>= 1.15.0)
211
+ railties (= 8.0.2)
212
+ rails-dom-testing (2.3.0)
213
+ activesupport (>= 5.0.0)
214
+ minitest
215
+ nokogiri (>= 1.6)
216
+ rails-html-sanitizer (1.6.2)
217
+ loofah (~> 2.21)
218
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
219
+ railties (8.0.2)
220
+ actionpack (= 8.0.2)
221
+ activesupport (= 8.0.2)
222
+ irb (~> 1.13)
223
+ rackup (>= 1.0.0)
224
+ rake (>= 12.2)
225
+ thor (~> 1.0, >= 1.2.2)
226
+ zeitwerk (~> 2.6)
227
+ rainbow (3.1.1)
228
+ rake (13.2.1)
229
+ rbs (3.9.4)
230
+ logger
231
+ rdoc (6.14.0)
232
+ erb
233
+ psych (>= 4.0.0)
234
+ redcarpet (3.6.1)
235
+ regexp_parser (2.10.0)
236
+ reline (0.6.1)
237
+ io-console (~> 0.5)
238
+ rexml (3.4.1)
239
+ rouge (4.5.2)
240
+ rubocop (1.71.0)
241
+ json (~> 2.3)
242
+ language_server-protocol (>= 3.17.0)
243
+ parallel (~> 1.10)
244
+ parser (>= 3.3.0.2)
245
+ rainbow (>= 2.2.2, < 4.0)
246
+ regexp_parser (>= 2.9.3, < 3.0)
247
+ rubocop-ast (>= 1.36.2, < 2.0)
248
+ ruby-progressbar (~> 1.7)
249
+ unicode-display_width (>= 2.4.0, < 4.0)
250
+ rubocop-ast (1.44.1)
251
+ parser (>= 3.3.7.2)
252
+ prism (~> 1.4)
253
+ rubocop-capybara (2.21.0)
254
+ rubocop (~> 1.41)
255
+ rubocop-performance (1.23.1)
256
+ rubocop (>= 1.48.1, < 2.0)
257
+ rubocop-ast (>= 1.31.1, < 2.0)
258
+ rubocop-rails (2.29.1)
259
+ activesupport (>= 4.2.0)
260
+ rack (>= 1.1)
261
+ rubocop (>= 1.52.0, < 2.0)
262
+ rubocop-ast (>= 1.31.1, < 2.0)
263
+ ruby-lsp (0.23.23)
264
+ language_server-protocol (~> 3.17.0)
265
+ prism (>= 1.2, < 2.0)
266
+ rbs (>= 3, < 5)
267
+ sorbet-runtime (>= 0.5.10782)
268
+ ruby-lsp-rails (0.4.3)
269
+ ruby-lsp (>= 0.23.18, < 0.24.0)
270
+ ruby-progressbar (1.13.0)
271
+ securerandom (0.4.1)
272
+ simplecov (0.22.0)
273
+ docile (~> 1.1)
274
+ simplecov-html (~> 0.11)
275
+ simplecov_json_formatter (~> 0.1)
276
+ simplecov-cobertura (2.1.0)
277
+ rexml
278
+ simplecov (~> 0.19)
279
+ simplecov-html (0.13.1)
280
+ simplecov_json_formatter (0.1.4)
281
+ sorbet-runtime (0.5.12130)
282
+ stringio (3.1.7)
283
+ terminal-table (3.0.2)
284
+ unicode-display_width (>= 1.1.1, < 3)
285
+ thor (1.3.2)
286
+ timeout (0.4.3)
287
+ tzinfo (2.0.6)
288
+ concurrent-ruby (~> 1.0)
289
+ unicode-display_width (2.6.0)
290
+ uri (1.0.3)
291
+ useragent (0.16.11)
292
+ view_component (3.21.0)
293
+ activesupport (>= 5.2.0, < 8.1)
294
+ concurrent-ruby (~> 1.0)
295
+ method_source (~> 1.0)
296
+ websocket-driver (0.8.0)
297
+ base64
298
+ websocket-extensions (>= 0.1.0)
299
+ websocket-extensions (0.1.5)
300
+ xpath (3.2.0)
301
+ nokogiri (~> 1.8)
302
+ yard (0.9.37)
303
+ zeitwerk (2.7.3)
304
+
305
+ PLATFORMS
306
+ aarch64-linux-gnu
307
+ aarch64-linux-musl
308
+ arm-linux-gnu
309
+ arm-linux-musl
310
+ arm64-darwin
311
+ x86_64-darwin
312
+ x86_64-linux-gnu
313
+ x86_64-linux-musl
314
+
315
+ DEPENDENCIES
316
+ capybara
317
+ daisy_components!
318
+ debug
319
+ lookbook (= 2.3.4)
320
+ nokogiri (~> 1.18)
321
+ puma
322
+ redcarpet (= 3.6.1)
323
+ rubocop (= 1.71.0)
324
+ rubocop-capybara (= 2.21.0)
325
+ rubocop-performance (= 1.23.1)
326
+ rubocop-rails (= 2.29.1)
327
+ ruby-lsp
328
+ ruby-lsp-rails
329
+ simplecov
330
+ simplecov-cobertura
331
+ terminal-table (~> 3.0.2)
332
+ view_component (= 3.21.0)
333
+
334
+ BUNDLED WITH
335
+ 2.6.2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ [![codecov](https://codecov.io/github/Nittarab/daisy_components/graph/badge.svg?token=7ZOMOEG3U9)](https://codecov.io/github/Nittarab/daisy_components)
2
+
3
+ # DaisyComponents
4
+
5
+ DaisyComponents is a Ruby gem that provides a collection of ViewComponents implementing the DaisyUI design system for Rails applications. It combines the power of ViewComponent, TailwindCSS, and DaisyUI to create reusable, maintainable UI components.
6
+
7
+ ## Features
8
+
9
+ - Built on top of ViewComponent for component-based architecture
10
+ - Implements DaisyUI's design system, based on TailwindCSS
11
+ - Live preview and documentation with [Lookbook](https://github.com/lookbook-hq/lookbook)
12
+ - Comprehensive test coverage with Minitest
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem "daisy_components"
20
+ ```
21
+
22
+ And then execute:
23
+ ```bash
24
+ $ bundle install
25
+ ```
26
+
27
+ ## Usage
28
+ The component library includes Lookbook for development and documentation:
29
+
30
+ 1. Start the development server:
31
+ ```bash
32
+ $ bin/rails server
33
+ ```
34
+
35
+ 2. Visit http://localhost:3000/lookbook to see the component documentation and playground.
36
+
37
+ ![alt text](<docs/assets/2025-01_screeshot_1.png>)
38
+ ![alt text](<docs/assets/2025-01_screeshot_2.png>)
39
+ ![alt text](<docs/assets/2025-01_screeshot_3.png>)
40
+
41
+ ## Testing
42
+
43
+ Run the test suite:
44
+
45
+ ```bash
46
+ $ bin/rails test
47
+ ```
48
+
49
+ With coverage:
50
+ ```bash
51
+ $ COVERAGE=true bin/rails test
52
+ ```
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it
57
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin feature/my-new-feature`)
60
+ 5. Create new Pull Request
61
+
62
+ ## License
63
+
64
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ load 'rails/tasks/statistics.rake'
6
+
7
+ require 'bundler/gem_tasks'
File without changes
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ # Button component implementing DaisyUI's button styles
5
+ #
6
+ # @example Basic usage
7
+ # <%= render(ButtonComponent.new(text: "Click me")) %>
8
+ #
9
+ # @example Primary variant
10
+ # <%= render(ButtonComponent.new(text: "Submit", variant: :primary)) %>
11
+ #
12
+ # @example Outline button
13
+ # <%= render(ButtonComponent.new(text: "Download", style: :outline)) %>
14
+ #
15
+ # @example Small size with icon
16
+ # <%= render(ButtonComponent.new(
17
+ # text: "Save",
18
+ # size: :sm,
19
+ # icon_start: helpers.check_icon
20
+ # )) %>
21
+ #
22
+ # @example Block-level button
23
+ # <%= render(ButtonComponent.new(
24
+ # text: "Full width",
25
+ # shape: :block
26
+ # )) %>
27
+ #
28
+ # @example Icon only button
29
+ # <%= render(ButtonComponent.new(
30
+ # icon_start: helpers.search_icon,
31
+ # shape: :square
32
+ # )) %>
33
+ #
34
+ # @example Loading state
35
+ # <%= render(ButtonComponent.new(
36
+ # text: "Processing...",
37
+ # loading: true
38
+ # )) %>
39
+ #
40
+ # @example Disabled state
41
+ # <%= render(ButtonComponent.new(
42
+ # text: "Disabled",
43
+ # disabled: true
44
+ # )) %>
45
+ #
46
+ # @example Active state
47
+ # <%= render(ButtonComponent.new(
48
+ # text: "Pressed",
49
+ # active: true
50
+ # )) %>
51
+ #
52
+ # @example With icons
53
+ # <%= render(ButtonComponent.new(
54
+ # text: "Submit",
55
+ # icon_start: helpers.check_icon("h-5 w-5"),
56
+ # variant: "primary"
57
+ # )) %>
58
+ #
59
+ # @example With both icons
60
+ # <%= render(ButtonComponent.new(
61
+ # text: "Next",
62
+ # icon_start: helpers.sync_icon("h-5 w-5"),
63
+ # icon_end: helpers.arrow_right_icon("h-5 w-5")
64
+ # )) %>
65
+ #
66
+ # @example With block content
67
+ # <%= render(ButtonComponent.new) do %>
68
+ # Complex <strong>content</strong>
69
+ # <% end %>
70
+ #
71
+ # @example As a link
72
+ # <%= render(ButtonComponent.new(
73
+ # text: "Visit site",
74
+ # href: "https://example.com",
75
+ # target: "_blank"
76
+ # )) %>
77
+ #
78
+ # @example Form submit button
79
+ # <%= render(ButtonComponent.new(
80
+ # text: "Submit",
81
+ # type: "submit",
82
+ # variant: "primary"
83
+ # )) %>
84
+ #
85
+ # @example Delete action with Turbo
86
+ # <%= render(ButtonComponent.new(
87
+ # text: "Delete",
88
+ # href: item_path(@item),
89
+ # method: :delete,
90
+ # variant: "error"
91
+ # )) %>
92
+ class Button < BaseComponent
93
+ renders_one :start_icon
94
+ renders_one :end_icon
95
+
96
+ # Available button colors from DaisyUI
97
+ COLORS = {
98
+ primary: 'btn-primary',
99
+ secondary: 'btn-secondary',
100
+ accent: 'btn-accent',
101
+ neutral: 'btn-neutral',
102
+ ghost: 'btn-ghost',
103
+ link: 'btn-link',
104
+ info: 'btn-info',
105
+ success: 'btn-success',
106
+ warning: 'btn-warning',
107
+ error: 'btn-error'
108
+ }.freeze
109
+
110
+ # Available button sizes from DaisyUI
111
+ SIZES = {
112
+ xl: 'btn-xl',
113
+ lg: 'btn-lg',
114
+ md: 'btn-md',
115
+ sm: 'btn-sm',
116
+ xs: 'btn-xs'
117
+ }.freeze
118
+
119
+ # Available button variants from DaisyUI
120
+ VARIANTS = {
121
+ outline: 'btn-outline',
122
+ soft: 'btn-soft',
123
+ dash: 'btn-dash',
124
+ ghost: 'btn-ghost',
125
+ link: 'btn-link'
126
+ }.freeze
127
+
128
+ # Available button shape modifiers
129
+ SHAPES = {
130
+ wide: 'btn-wide',
131
+ block: 'btn-block',
132
+ circle: 'btn-circle',
133
+ square: 'btn-square'
134
+ }.freeze
135
+
136
+ # Valid HTML button types
137
+ BUTTON_TYPES = %w[button submit reset].freeze
138
+
139
+ # @param tag_type [Symbol] HTML tag to use (:button, :input, :a)
140
+ # @param text [String] The text content to display inside the button
141
+ # @param color [String] Visual style of the button
142
+ # (neutral/primary/secondary/accent/info/success/warning/error/ghost/link)
143
+ # @param size [String] Size of the button (xl/lg/md/sm/xs)
144
+ # @param variant [String] Variant of the button (outline/soft/dash)
145
+ # @param shape [String] Shape modifier of the button (wide/block/circle/square)
146
+ # @param disabled [Boolean] When true, prevents user interaction and grays out the button
147
+ # @param href [String] Turns the button into a link pointing to this URL
148
+ # @param type [String] HTML button type attribute (button/submit/reset)
149
+ # @param method [String] HTTP method for Rails/Turbo links (get/post/put/patch/delete)
150
+ # @param target [String] Link target attribute (_blank/_self/_parent/_top)
151
+ # @param rel [String] Link relationship attribute (e.g., noopener, noreferrer)
152
+ # @param loading [Boolean] When true, shows a loading spinner and disables the button
153
+ # @param active [Boolean] When true, gives the button a pressed appearance
154
+ # @param icon_start [String] SVG icon to display before the text
155
+ # @param icon_end [String] SVG icon to display after the text
156
+ # @param system_arguments [Hash] Additional HTML attributes to be applied to the button
157
+ def initialize( # rubocop:disable Metrics/ParameterLists
158
+ tag_type: :button,
159
+ text: nil,
160
+ color: nil,
161
+ size: nil,
162
+ variant: nil,
163
+ shape: nil,
164
+ disabled: false,
165
+ href: nil,
166
+ type: nil,
167
+ method: nil,
168
+ target: nil,
169
+ rel: nil,
170
+ loading: false,
171
+ active: false,
172
+ icon_start: nil,
173
+ icon_end: nil,
174
+ **system_arguments
175
+ )
176
+ @tag_type = tag_type
177
+ @color = build_argument(color, COLORS, 'color')
178
+ @size = build_argument(size, SIZES, 'size')
179
+ @variant = build_argument(variant, VARIANTS, 'variant')
180
+ @shape = build_argument(shape, SHAPES, 'shape')
181
+ @disabled = disabled
182
+ @href = href
183
+ @method = method
184
+ @target = target
185
+ @rel = rel
186
+ @loading = loading
187
+ @active = active
188
+ @type = type
189
+ @text = text
190
+
191
+ with_start_icon { icon_start } if icon_start
192
+ with_end_icon { icon_end } if icon_end
193
+ super(**system_arguments)
194
+ end
195
+
196
+ def call
197
+ tag.send(@tag_type, **full_arguments) { button_content }
198
+ end
199
+
200
+ private
201
+
202
+ def full_arguments
203
+ base = {
204
+ class: computed_classes,
205
+ disabled: @disabled,
206
+ **system_arguments.except(:class)
207
+ }
208
+
209
+ @href || @tag_type.to_s == 'a' ? link_specific_arguments(base) : button_specific_arguments(base)
210
+ end
211
+
212
+ # Order: base -> style/state(active) -> variant -> size -> shape
213
+ def computed_classes
214
+ modifiers = ['btn']
215
+ modifiers << @variant
216
+ modifiers << 'btn-active' if @active
217
+ modifiers << @color
218
+ modifiers << @size
219
+ modifiers << @shape
220
+
221
+ class_names(modifiers, system_arguments[:class])
222
+ end
223
+
224
+ def button_specific_arguments(base)
225
+ type = if %w[button input].include?(@tag_type.to_s)
226
+ @type || 'button'
227
+ else
228
+ @type
229
+ end
230
+ base.merge(type: type).compact
231
+ end
232
+
233
+ def link_specific_arguments(base)
234
+ base.merge(
235
+ role: 'button',
236
+ href: @href,
237
+ data: { turbo_method: @method }.compact,
238
+ target: @target,
239
+ rel: link_rel
240
+ ).compact
241
+ end
242
+
243
+ def button_content
244
+ safe_join([
245
+ loading_spinner,
246
+ start_icon,
247
+ content || @text,
248
+ end_icon
249
+ ].compact)
250
+ end
251
+
252
+ def link_rel
253
+ return @rel if @rel
254
+
255
+ 'noopener noreferrer' if @target == '_blank'
256
+ end
257
+
258
+ def loading_spinner
259
+ tag.span(class: 'loading loading-spinner') if @loading
260
+ end
261
+ end
262
+ end