better_ui 0.6.0 → 0.7.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.
Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +257 -212
  3. data/Rakefile +11 -2
  4. data/app/components/better_ui/action_messages_component/action_messages_component.html.erb +48 -0
  5. data/app/components/better_ui/action_messages_component.rb +544 -0
  6. data/app/components/better_ui/application_component.rb +66 -0
  7. data/app/components/better_ui/button_component/button_component.html.erb +31 -0
  8. data/app/components/better_ui/button_component.rb +307 -0
  9. data/app/components/better_ui/card_component/card_component.html.erb +17 -0
  10. data/app/components/better_ui/card_component.rb +460 -0
  11. data/app/components/better_ui/drawer/header_component/header_component.html.erb +24 -0
  12. data/app/components/better_ui/drawer/header_component.rb +238 -0
  13. data/app/components/better_ui/drawer/layout_component/layout_component.html.erb +44 -0
  14. data/app/components/better_ui/drawer/layout_component.rb +270 -0
  15. data/app/components/better_ui/drawer/nav_group_component/nav_group_component.html.erb +10 -0
  16. data/app/components/better_ui/drawer/nav_group_component.rb +155 -0
  17. data/app/components/better_ui/drawer/nav_item_component/nav_item_component.html.erb +13 -0
  18. data/app/components/better_ui/drawer/nav_item_component.rb +225 -0
  19. data/app/components/better_ui/drawer/sidebar_component/sidebar_component.html.erb +17 -0
  20. data/app/components/better_ui/drawer/sidebar_component.rb +263 -0
  21. data/app/components/better_ui/forms/base_component.rb +450 -0
  22. data/app/components/better_ui/forms/checkbox_component/checkbox_component.html.erb +28 -0
  23. data/app/components/better_ui/forms/checkbox_component.rb +419 -0
  24. data/app/components/better_ui/forms/checkbox_group_component/checkbox_group_component.html.erb +40 -0
  25. data/app/components/better_ui/forms/checkbox_group_component.rb +363 -0
  26. data/app/components/better_ui/forms/number_input_component/number_input_component.html.erb +40 -0
  27. data/app/components/better_ui/forms/number_input_component.rb +320 -0
  28. data/app/components/better_ui/forms/password_input_component/password_input_component.html.erb +71 -0
  29. data/app/components/better_ui/forms/password_input_component.rb +206 -0
  30. data/app/components/better_ui/forms/text_input_component/text_input_component.html.erb +40 -0
  31. data/app/components/better_ui/forms/text_input_component.rb +258 -0
  32. data/app/components/better_ui/forms/textarea_component/textarea_component.html.erb +40 -0
  33. data/app/components/better_ui/forms/textarea_component.rb +329 -0
  34. data/app/form_builders/better_ui/ui_form_builder.rb +467 -0
  35. data/app/helpers/better_ui/application_helper.rb +325 -58
  36. data/app/views/layouts/better_ui/application.html.erb +1 -1
  37. data/config/routes.rb +1 -0
  38. data/lib/better_ui/engine.rb +34 -5
  39. data/lib/better_ui/version.rb +1 -1
  40. data/lib/better_ui.rb +32 -5
  41. data/lib/generators/better_ui/install/USAGE +44 -0
  42. data/lib/generators/better_ui/install/install_generator.rb +87 -0
  43. data/lib/generators/better_ui/install/templates/better_ui_theme.css.tt +280 -0
  44. data/lib/tasks/better_ui_tasks.rake +39 -4
  45. metadata +55 -203
  46. data/app/components/better_ui/application/card/component.html.erb +0 -20
  47. data/app/components/better_ui/application/card/component.rb +0 -214
  48. data/app/components/better_ui/application/main/component.html.erb +0 -9
  49. data/app/components/better_ui/application/main/component.rb +0 -123
  50. data/app/components/better_ui/application/navbar/component.html.erb +0 -92
  51. data/app/components/better_ui/application/navbar/component.rb +0 -136
  52. data/app/components/better_ui/application/sidebar/component.html.erb +0 -249
  53. data/app/components/better_ui/application/sidebar/component.rb +0 -187
  54. data/app/components/better_ui/general/accordion/component.html.erb +0 -5
  55. data/app/components/better_ui/general/accordion/component.rb +0 -92
  56. data/app/components/better_ui/general/accordion/item_component.html.erb +0 -12
  57. data/app/components/better_ui/general/accordion/item_component.rb +0 -176
  58. data/app/components/better_ui/general/alert/component.html.erb +0 -32
  59. data/app/components/better_ui/general/alert/component.rb +0 -242
  60. data/app/components/better_ui/general/avatar/component.html.erb +0 -20
  61. data/app/components/better_ui/general/avatar/component.rb +0 -301
  62. data/app/components/better_ui/general/badge/component.html.erb +0 -23
  63. data/app/components/better_ui/general/badge/component.rb +0 -248
  64. data/app/components/better_ui/general/breadcrumb/component.html.erb +0 -15
  65. data/app/components/better_ui/general/breadcrumb/component.rb +0 -187
  66. data/app/components/better_ui/general/button/component.html.erb +0 -34
  67. data/app/components/better_ui/general/button/component.rb +0 -214
  68. data/app/components/better_ui/general/divider/component.html.erb +0 -10
  69. data/app/components/better_ui/general/divider/component.rb +0 -226
  70. data/app/components/better_ui/general/dropdown/component.html.erb +0 -28
  71. data/app/components/better_ui/general/dropdown/component.rb +0 -192
  72. data/app/components/better_ui/general/dropdown/divider_component.html.erb +0 -1
  73. data/app/components/better_ui/general/dropdown/divider_component.rb +0 -41
  74. data/app/components/better_ui/general/dropdown/item_component.html.erb +0 -6
  75. data/app/components/better_ui/general/dropdown/item_component.rb +0 -119
  76. data/app/components/better_ui/general/field/component.html.erb +0 -27
  77. data/app/components/better_ui/general/field/component.rb +0 -37
  78. data/app/components/better_ui/general/grid/cell_component.html.erb +0 -3
  79. data/app/components/better_ui/general/grid/cell_component.rb +0 -390
  80. data/app/components/better_ui/general/grid/component.html.erb +0 -3
  81. data/app/components/better_ui/general/grid/component.rb +0 -301
  82. data/app/components/better_ui/general/heading/component.html.erb +0 -22
  83. data/app/components/better_ui/general/heading/component.rb +0 -257
  84. data/app/components/better_ui/general/icon/component.html.erb +0 -7
  85. data/app/components/better_ui/general/icon/component.rb +0 -240
  86. data/app/components/better_ui/general/input/checkbox/component.html.erb +0 -5
  87. data/app/components/better_ui/general/input/checkbox/component.rb +0 -238
  88. data/app/components/better_ui/general/input/datetime/component.html.erb +0 -5
  89. data/app/components/better_ui/general/input/datetime/component.rb +0 -223
  90. data/app/components/better_ui/general/input/pin/component.html.erb +0 -1
  91. data/app/components/better_ui/general/input/pin/component.rb +0 -201
  92. data/app/components/better_ui/general/input/radio/component.html.erb +0 -5
  93. data/app/components/better_ui/general/input/radio/component.rb +0 -230
  94. data/app/components/better_ui/general/input/rating/component.html.erb +0 -4
  95. data/app/components/better_ui/general/input/rating/component.rb +0 -272
  96. data/app/components/better_ui/general/input/select/component.html.erb +0 -78
  97. data/app/components/better_ui/general/input/select/component.rb +0 -249
  98. data/app/components/better_ui/general/input/select/select_component.html.erb +0 -5
  99. data/app/components/better_ui/general/input/select/select_component.rb +0 -37
  100. data/app/components/better_ui/general/input/text/component.html.erb +0 -5
  101. data/app/components/better_ui/general/input/text/component.rb +0 -171
  102. data/app/components/better_ui/general/input/textarea/component.html.erb +0 -5
  103. data/app/components/better_ui/general/input/textarea/component.rb +0 -166
  104. data/app/components/better_ui/general/input/toggle/component.html.erb +0 -5
  105. data/app/components/better_ui/general/input/toggle/component.rb +0 -242
  106. data/app/components/better_ui/general/link/component.html.erb +0 -18
  107. data/app/components/better_ui/general/link/component.rb +0 -258
  108. data/app/components/better_ui/general/modal/component.html.erb +0 -5
  109. data/app/components/better_ui/general/modal/component.rb +0 -47
  110. data/app/components/better_ui/general/modal/modal_component.html.erb +0 -52
  111. data/app/components/better_ui/general/modal/modal_component.rb +0 -160
  112. data/app/components/better_ui/general/pagination/component.html.erb +0 -85
  113. data/app/components/better_ui/general/pagination/component.rb +0 -216
  114. data/app/components/better_ui/general/panel/component.html.erb +0 -28
  115. data/app/components/better_ui/general/panel/component.rb +0 -249
  116. data/app/components/better_ui/general/progress/component.html.erb +0 -11
  117. data/app/components/better_ui/general/progress/component.rb +0 -160
  118. data/app/components/better_ui/general/spinner/component.html.erb +0 -35
  119. data/app/components/better_ui/general/spinner/component.rb +0 -93
  120. data/app/components/better_ui/general/table/component.html.erb +0 -5
  121. data/app/components/better_ui/general/table/component.rb +0 -217
  122. data/app/components/better_ui/general/table/tbody_component.html.erb +0 -3
  123. data/app/components/better_ui/general/table/tbody_component.rb +0 -30
  124. data/app/components/better_ui/general/table/td_component.html.erb +0 -3
  125. data/app/components/better_ui/general/table/td_component.rb +0 -44
  126. data/app/components/better_ui/general/table/tfoot_component.html.erb +0 -3
  127. data/app/components/better_ui/general/table/tfoot_component.rb +0 -28
  128. data/app/components/better_ui/general/table/th_component.html.erb +0 -6
  129. data/app/components/better_ui/general/table/th_component.rb +0 -51
  130. data/app/components/better_ui/general/table/thead_component.html.erb +0 -3
  131. data/app/components/better_ui/general/table/thead_component.rb +0 -28
  132. data/app/components/better_ui/general/table/tr_component.html.erb +0 -3
  133. data/app/components/better_ui/general/table/tr_component.rb +0 -30
  134. data/app/components/better_ui/general/tabs/component.html.erb +0 -11
  135. data/app/components/better_ui/general/tabs/component.rb +0 -120
  136. data/app/components/better_ui/general/tabs/panel_component.html.erb +0 -3
  137. data/app/components/better_ui/general/tabs/panel_component.rb +0 -37
  138. data/app/components/better_ui/general/tabs/tab_component.html.erb +0 -13
  139. data/app/components/better_ui/general/tabs/tab_component.rb +0 -111
  140. data/app/components/better_ui/general/tag/component.html.erb +0 -3
  141. data/app/components/better_ui/general/tag/component.rb +0 -104
  142. data/app/components/better_ui/general/text/component.html.erb +0 -1
  143. data/app/components/better_ui/general/text/component.rb +0 -194
  144. data/app/components/better_ui/general/tooltip/component.html.erb +0 -7
  145. data/app/components/better_ui/general/tooltip/component.rb +0 -239
  146. data/app/helpers/better_ui/application/components/card/card_helper.rb +0 -96
  147. data/app/helpers/better_ui/application/components/card.rb +0 -11
  148. data/app/helpers/better_ui/application/components/main/main_helper.rb +0 -64
  149. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +0 -77
  150. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +0 -51
  151. data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +0 -73
  152. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +0 -57
  153. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +0 -29
  154. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +0 -53
  155. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +0 -37
  156. data/app/helpers/better_ui/general/components/button/button_helper.rb +0 -65
  157. data/app/helpers/better_ui/general/components/container/container_helper.rb +0 -60
  158. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +0 -63
  159. data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +0 -32
  160. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +0 -88
  161. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +0 -68
  162. data/app/helpers/better_ui/general/components/field/field_helper.rb +0 -26
  163. data/app/helpers/better_ui/general/components/grid/grid_helper.rb +0 -145
  164. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +0 -72
  165. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +0 -16
  166. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +0 -81
  167. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +0 -91
  168. data/app/helpers/better_ui/general/components/input/pin/pin_helper.rb +0 -76
  169. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +0 -79
  170. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +0 -124
  171. data/app/helpers/better_ui/general/components/input/rating/rating_helper.rb +0 -70
  172. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +0 -86
  173. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +0 -138
  174. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +0 -73
  175. data/app/helpers/better_ui/general/components/input/toggle/toggle_helper.rb +0 -77
  176. data/app/helpers/better_ui/general/components/link/link_helper.rb +0 -89
  177. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +0 -85
  178. data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +0 -82
  179. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +0 -83
  180. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +0 -53
  181. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +0 -19
  182. data/app/helpers/better_ui/general/components/table/table_helper.rb +0 -53
  183. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +0 -13
  184. data/app/helpers/better_ui/general/components/table/td_helper.rb +0 -19
  185. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +0 -13
  186. data/app/helpers/better_ui/general/components/table/th_helper.rb +0 -19
  187. data/app/helpers/better_ui/general/components/table/thead_helper.rb +0 -13
  188. data/app/helpers/better_ui/general/components/table/tr_helper.rb +0 -13
  189. data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +0 -62
  190. data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +0 -55
  191. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +0 -95
  192. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +0 -26
  193. data/app/helpers/better_ui/general/components/text/text_helper.rb +0 -83
  194. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +0 -60
  195. data/app/jobs/better_ui/application_job.rb +0 -4
  196. data/app/mailers/better_ui/application_mailer.rb +0 -6
  197. data/config/initializers/lookbook.rb +0 -23
  198. data/lib/better_ui/railtie.rb +0 -20
@@ -0,0 +1,460 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ # A flexible card component for displaying content in a styled container.
5
+ #
6
+ # This component provides a versatile box container with support for multiple color variants,
7
+ # visual styles, sizes, and optional header/footer sections. Perfect for displaying grouped
8
+ # content, information panels, dashboards, and feature highlights.
9
+ #
10
+ # @example Basic card
11
+ # <%= render BetterUi::CardComponent.new do %>
12
+ # Card content here
13
+ # <% end %>
14
+ #
15
+ # @example Card with header and footer
16
+ # <%= render BetterUi::CardComponent.new(variant: :primary, style: :outline) do |card| %>
17
+ # <% card.with_header { "Card Title" } %>
18
+ # <% card.with_body { "Main content goes here" } %>
19
+ # <% card.with_footer { "Footer actions" } %>
20
+ # <% end %>
21
+ #
22
+ # @example Transparent card
23
+ # <%= render BetterUi::CardComponent.new(style: :ghost) do %>
24
+ # Transparent card content
25
+ # <% end %>
26
+ #
27
+ # @example Large success card without shadow
28
+ # <%= render BetterUi::CardComponent.new(variant: :success, size: :lg, shadow: false) do |card| %>
29
+ # <% card.with_header { "Success!" } %>
30
+ # <% card.with_body { "Operation completed successfully" } %>
31
+ # <% end %>
32
+ #
33
+ # @example Different styles
34
+ # # Solid (default): Filled colored background
35
+ # <%= render BetterUi::CardComponent.new(style: :solid) { "Solid card" } %>
36
+ #
37
+ # # Outline: White background with colored border
38
+ # <%= render BetterUi::CardComponent.new(style: :outline) { "Outline card" } %>
39
+ #
40
+ # # Ghost: Transparent background
41
+ # <%= render BetterUi::CardComponent.new(style: :ghost) { "Ghost card" } %>
42
+ #
43
+ # # Soft: Light colored background
44
+ # <%= render BetterUi::CardComponent.new(style: :soft) { "Soft card" } %>
45
+ #
46
+ # # Bordered: Neutral gray border (variant-agnostic)
47
+ # <%= render BetterUi::CardComponent.new(style: :bordered) { "Bordered card" } %>
48
+ class CardComponent < ApplicationComponent
49
+ # Size configurations for padding, text, and border radius
50
+ SIZES = {
51
+ xs: { padding: "p-3", text: "text-xs", radius: "rounded" },
52
+ sm: { padding: "p-4", text: "text-sm", radius: "rounded-md" },
53
+ md: { padding: "p-6", text: "text-base", radius: "rounded-lg" },
54
+ lg: { padding: "p-8", text: "text-lg", radius: "rounded-lg" },
55
+ xl: { padding: "p-10", text: "text-xl", radius: "rounded-xl" }
56
+ }.freeze
57
+
58
+ # Available visual styles
59
+ STYLES = %i[solid outline ghost soft bordered].freeze
60
+
61
+ # @!method with_header
62
+ # Slot for rendering optional header content at the top of the card.
63
+ # The header is separated from the body with a divider.
64
+ # @yieldreturn [String] the HTML content for the header
65
+ renders_one :header
66
+
67
+ # @!method with_body
68
+ # Slot for rendering the main body content of the card.
69
+ # If not provided, the card will render its default content in the body.
70
+ # @yieldreturn [String] the HTML content for the body
71
+ renders_one :body
72
+
73
+ # @!method with_footer
74
+ # Slot for rendering optional footer content at the bottom of the card.
75
+ # The footer is separated from the body with a divider.
76
+ # @yieldreturn [String] the HTML content for the footer
77
+ renders_one :footer
78
+
79
+ # Initializes a new card component.
80
+ #
81
+ # @param variant [Symbol] the color variant (:primary, :secondary, :accent, :success, :danger, :warning, :info, :light, :dark), defaults to :primary
82
+ # @param style [Symbol] the visual style (:solid, :outline, :ghost, :soft, :bordered), defaults to :solid
83
+ # @param size [Symbol] the size variant (:xs, :sm, :md, :lg, :xl), defaults to :md
84
+ # @param shadow [Boolean] whether to apply shadow, defaults to true
85
+ # @param header_padding [Boolean] whether to apply padding to header section, defaults to true
86
+ # @param body_padding [Boolean] whether to apply padding to body section, defaults to true
87
+ # @param footer_padding [Boolean] whether to apply padding to footer section, defaults to true
88
+ # @param container_classes [String, nil] additional CSS classes for the outer wrapper
89
+ # @param header_classes [String, nil] additional CSS classes for the header section
90
+ # @param body_classes [String, nil] additional CSS classes for the body section
91
+ # @param footer_classes [String, nil] additional CSS classes for the footer section
92
+ # @param options [Hash] additional HTML attributes passed to the card element
93
+ #
94
+ # @raise [ArgumentError] if variant is not one of the allowed values
95
+ # @raise [ArgumentError] if style is not one of the allowed values
96
+ # @raise [ArgumentError] if size is not one of the allowed values
97
+ #
98
+ # @example With all options
99
+ # <%= render BetterUi::CardComponent.new(
100
+ # variant: :primary,
101
+ # style: :outline,
102
+ # size: :lg,
103
+ # shadow: true,
104
+ # container_classes: "mb-4",
105
+ # id: "my-card",
106
+ # data: { controller: "custom" }
107
+ # ) do |card| %>
108
+ # <% card.with_header { "Title" } %>
109
+ # <% card.with_body { "Content" } %>
110
+ # <% end %>
111
+ #
112
+ # @example Full-width image with no body padding
113
+ # <%= render BetterUi::CardComponent.new(body_padding: false) do |card| %>
114
+ # <% card.with_header { "Gallery" } %>
115
+ # <% card.with_body do %>
116
+ # <%= image_tag "photo.jpg", class: "w-full" %>
117
+ # <% end %>
118
+ # <% end %>
119
+ #
120
+ # @example Table container with no padding
121
+ # <%= render BetterUi::CardComponent.new(
122
+ # body_padding: false,
123
+ # style: :bordered
124
+ # ) do |card| %>
125
+ # <% card.with_body do %>
126
+ # <table class="w-full">...</table>
127
+ # <% end %>
128
+ # <% end %>
129
+ def initialize(
130
+ variant: :primary,
131
+ style: :solid,
132
+ size: :md,
133
+ shadow: true,
134
+ header_padding: true,
135
+ body_padding: true,
136
+ footer_padding: true,
137
+ container_classes: nil,
138
+ header_classes: nil,
139
+ body_classes: nil,
140
+ footer_classes: nil,
141
+ **options
142
+ )
143
+ @variant = validate_variant(variant)
144
+ @style = validate_style(style)
145
+ @size = validate_size(size)
146
+ @shadow = shadow
147
+ @header_padding = header_padding
148
+ @body_padding = body_padding
149
+ @footer_padding = footer_padding
150
+ @container_classes = container_classes
151
+ @header_classes = header_classes
152
+ @body_classes = body_classes
153
+ @footer_classes = footer_classes
154
+ @options = options
155
+ end
156
+
157
+ private
158
+
159
+ # Returns the complete CSS classes for the card container.
160
+ #
161
+ # @return [String] the merged CSS class string
162
+ # @api private
163
+ def component_classes
164
+ css_classes([
165
+ "flex",
166
+ "flex-col",
167
+ size_config[:radius],
168
+ size_config[:text],
169
+ style_classes,
170
+ shadow_classes,
171
+ @container_classes
172
+ ].flatten.compact)
173
+ end
174
+
175
+ # Returns CSS classes specific to the selected style.
176
+ #
177
+ # @return [Array<String>] array of CSS classes for the style
178
+ # @api private
179
+ def style_classes
180
+ case @style
181
+ when :solid then solid_classes
182
+ when :outline then outline_classes
183
+ when :ghost then ghost_classes
184
+ when :soft then soft_classes
185
+ when :bordered then bordered_classes
186
+ end
187
+ end
188
+
189
+ # Returns CSS classes for solid style.
190
+ # Filled background with subtle border and dark text.
191
+ #
192
+ # @return [Array<String>] array of CSS classes
193
+ # @api private
194
+ def solid_classes
195
+ case @variant
196
+ when :primary
197
+ [ "bg-primary-50", "border", "border-primary-200", "text-primary-900" ]
198
+ when :secondary
199
+ [ "bg-secondary-50", "border", "border-secondary-200", "text-secondary-900" ]
200
+ when :accent
201
+ [ "bg-accent-50", "border", "border-accent-200", "text-accent-900" ]
202
+ when :success
203
+ [ "bg-success-50", "border", "border-success-200", "text-success-900" ]
204
+ when :danger
205
+ [ "bg-danger-50", "border", "border-danger-200", "text-danger-900" ]
206
+ when :warning
207
+ [ "bg-warning-50", "border", "border-warning-200", "text-warning-900" ]
208
+ when :info
209
+ [ "bg-info-50", "border", "border-info-200", "text-info-900" ]
210
+ when :light
211
+ [ "bg-light", "border", "border-grayscale-200", "text-grayscale-900" ]
212
+ when :dark
213
+ [ "bg-dark", "border", "border-grayscale-700", "text-grayscale-50" ]
214
+ end
215
+ end
216
+
217
+ # Returns CSS classes for outline style.
218
+ # White background with colored border.
219
+ #
220
+ # @return [Array<String>] array of CSS classes
221
+ # @api private
222
+ def outline_classes
223
+ case @variant
224
+ when :primary
225
+ [ "bg-white", "border-2", "border-primary-500", "text-primary-700" ]
226
+ when :secondary
227
+ [ "bg-white", "border-2", "border-secondary-500", "text-secondary-700" ]
228
+ when :accent
229
+ [ "bg-white", "border-2", "border-accent-500", "text-accent-700" ]
230
+ when :success
231
+ [ "bg-white", "border-2", "border-success-500", "text-success-700" ]
232
+ when :danger
233
+ [ "bg-white", "border-2", "border-danger-500", "text-danger-700" ]
234
+ when :warning
235
+ [ "bg-white", "border-2", "border-warning-500", "text-warning-700" ]
236
+ when :info
237
+ [ "bg-white", "border-2", "border-info-500", "text-info-700" ]
238
+ when :light
239
+ [ "bg-white", "border-2", "border-grayscale-300", "text-grayscale-700" ]
240
+ when :dark
241
+ [ "bg-white", "border-2", "border-grayscale-900", "text-grayscale-900" ]
242
+ end
243
+ end
244
+
245
+ # Returns CSS classes for ghost style.
246
+ # Transparent background with colored text (for transparent cards).
247
+ #
248
+ # @return [Array<String>] array of CSS classes
249
+ # @api private
250
+ def ghost_classes
251
+ case @variant
252
+ when :primary
253
+ [ "bg-transparent", "text-primary-600" ]
254
+ when :secondary
255
+ [ "bg-transparent", "text-secondary-600" ]
256
+ when :accent
257
+ [ "bg-transparent", "text-accent-600" ]
258
+ when :success
259
+ [ "bg-transparent", "text-success-600" ]
260
+ when :danger
261
+ [ "bg-transparent", "text-danger-600" ]
262
+ when :warning
263
+ [ "bg-transparent", "text-warning-600" ]
264
+ when :info
265
+ [ "bg-transparent", "text-info-600" ]
266
+ when :light
267
+ [ "bg-transparent", "text-grayscale-400" ]
268
+ when :dark
269
+ [ "bg-transparent", "text-grayscale-900" ]
270
+ end
271
+ end
272
+
273
+ # Returns CSS classes for soft style.
274
+ # Light background with subtle border and medium text.
275
+ #
276
+ # @return [Array<String>] array of CSS classes
277
+ # @api private
278
+ def soft_classes
279
+ case @variant
280
+ when :primary
281
+ [ "bg-primary-50", "border", "border-primary-100", "text-primary-800" ]
282
+ when :secondary
283
+ [ "bg-secondary-50", "border", "border-secondary-100", "text-secondary-800" ]
284
+ when :accent
285
+ [ "bg-accent-50", "border", "border-accent-100", "text-accent-800" ]
286
+ when :success
287
+ [ "bg-success-50", "border", "border-success-100", "text-success-800" ]
288
+ when :danger
289
+ [ "bg-danger-50", "border", "border-danger-100", "text-danger-800" ]
290
+ when :warning
291
+ [ "bg-warning-50", "border", "border-warning-100", "text-warning-800" ]
292
+ when :info
293
+ [ "bg-info-50", "border", "border-info-100", "text-info-800" ]
294
+ when :light
295
+ [ "bg-light", "border", "border-grayscale-100", "text-grayscale-800" ]
296
+ when :dark
297
+ [ "bg-grayscale-800", "border", "border-grayscale-700", "text-grayscale-100" ]
298
+ end
299
+ end
300
+
301
+ # Returns CSS classes for bordered style.
302
+ # Neutral white background with gray border, variant-agnostic.
303
+ # Perfect for visual content separation and isolation.
304
+ #
305
+ # @return [Array<String>] array of CSS classes
306
+ # @api private
307
+ def bordered_classes
308
+ [ "bg-white", "border", "border-gray-300", "text-gray-900" ]
309
+ end
310
+
311
+ # Returns shadow CSS classes based on the shadow parameter.
312
+ #
313
+ # @return [String, nil] shadow class or nil
314
+ # @api private
315
+ def shadow_classes
316
+ @shadow ? "shadow-md" : nil
317
+ end
318
+
319
+ # Returns the size configuration hash for the current size.
320
+ #
321
+ # @return [Hash] size configuration with padding, text, radius, and gap
322
+ # @api private
323
+ def size_config
324
+ SIZES[@size]
325
+ end
326
+
327
+ # Returns CSS classes for the header section.
328
+ #
329
+ # @return [String] CSS classes for header
330
+ # @api private
331
+ def header_wrapper_classes
332
+ css_classes([
333
+ (@header_padding ? size_config[:padding] : nil),
334
+ "border-b",
335
+ border_color_class,
336
+ @header_classes
337
+ ].flatten.compact)
338
+ end
339
+
340
+ # Returns CSS classes for the body section.
341
+ #
342
+ # @return [String] CSS classes for body
343
+ # @api private
344
+ def body_wrapper_classes
345
+ css_classes([
346
+ (@body_padding ? size_config[:padding] : nil),
347
+ @body_classes
348
+ ].flatten.compact)
349
+ end
350
+
351
+ # Returns CSS classes for the footer section.
352
+ #
353
+ # @return [String] CSS classes for footer
354
+ # @api private
355
+ def footer_wrapper_classes
356
+ css_classes([
357
+ (@footer_padding ? size_config[:padding] : nil),
358
+ "border-t",
359
+ border_color_class,
360
+ @footer_classes
361
+ ].flatten.compact)
362
+ end
363
+
364
+ # Returns the appropriate border color class based on variant and style.
365
+ #
366
+ # @return [String] border color class
367
+ # @api private
368
+ def border_color_class
369
+ case @style
370
+ when :solid
371
+ case @variant
372
+ when :primary then "border-primary-200"
373
+ when :secondary then "border-secondary-200"
374
+ when :accent then "border-accent-200"
375
+ when :success then "border-success-200"
376
+ when :danger then "border-danger-200"
377
+ when :warning then "border-warning-200"
378
+ when :info then "border-info-200"
379
+ when :light then "border-grayscale-200"
380
+ when :dark then "border-grayscale-700"
381
+ end
382
+ when :outline
383
+ case @variant
384
+ when :primary then "border-primary-500"
385
+ when :secondary then "border-secondary-500"
386
+ when :accent then "border-accent-500"
387
+ when :success then "border-success-500"
388
+ when :danger then "border-danger-500"
389
+ when :warning then "border-warning-500"
390
+ when :info then "border-info-500"
391
+ when :light then "border-grayscale-300"
392
+ when :dark then "border-grayscale-900"
393
+ end
394
+ when :soft
395
+ case @variant
396
+ when :primary then "border-primary-100"
397
+ when :secondary then "border-secondary-100"
398
+ when :accent then "border-accent-100"
399
+ when :success then "border-success-100"
400
+ when :danger then "border-danger-100"
401
+ when :warning then "border-warning-100"
402
+ when :info then "border-info-100"
403
+ when :light then "border-grayscale-100"
404
+ when :dark then "border-grayscale-700"
405
+ end
406
+ when :ghost
407
+ "border-transparent"
408
+ when :bordered
409
+ "border-gray-300"
410
+ end
411
+ end
412
+
413
+ # Returns HTML attributes for the card element.
414
+ #
415
+ # @return [Hash] HTML attributes hash
416
+ # @api private
417
+ def html_attributes
418
+ @options
419
+ end
420
+
421
+ # Validates the variant parameter.
422
+ #
423
+ # @param variant [Symbol] the variant to validate
424
+ # @return [Symbol] the validated variant
425
+ # @raise [ArgumentError] if variant is invalid
426
+ # @api private
427
+ def validate_variant(variant)
428
+ unless BetterUi::ApplicationComponent::VARIANTS.key?(variant)
429
+ raise ArgumentError, "Invalid variant: #{variant}. Must be one of: #{BetterUi::ApplicationComponent::VARIANTS.keys.join(', ')}"
430
+ end
431
+ variant
432
+ end
433
+
434
+ # Validates the style parameter.
435
+ #
436
+ # @param style [Symbol] the style to validate
437
+ # @return [Symbol] the validated style
438
+ # @raise [ArgumentError] if style is invalid
439
+ # @api private
440
+ def validate_style(style)
441
+ unless STYLES.include?(style)
442
+ raise ArgumentError, "Invalid style: #{style}. Must be one of: #{STYLES.join(', ')}"
443
+ end
444
+ style
445
+ end
446
+
447
+ # Validates the size parameter.
448
+ #
449
+ # @param size [Symbol] the size to validate
450
+ # @return [Symbol] the validated size
451
+ # @raise [ArgumentError] if size is invalid
452
+ # @api private
453
+ def validate_size(size)
454
+ unless SIZES.key?(size)
455
+ raise ArgumentError, "Invalid size: #{size}. Must be one of: #{SIZES.keys.join(', ')}"
456
+ end
457
+ size
458
+ end
459
+ end
460
+ end
@@ -0,0 +1,24 @@
1
+ <header class="<%= component_classes %>" <%= tag.attributes(html_attributes) %>>
2
+ <div class="<%= logo_classes %>">
3
+ <% if mobile_menu_button? %>
4
+ <div class="<%= mobile_menu_button_classes %> mr-2">
5
+ <%= mobile_menu_button %>
6
+ </div>
7
+ <% end %>
8
+ <% if logo? %>
9
+ <%= logo %>
10
+ <% end %>
11
+ </div>
12
+
13
+ <% if navigation? %>
14
+ <div class="<%= navigation_classes %>">
15
+ <%= navigation %>
16
+ </div>
17
+ <% end %>
18
+
19
+ <% if actions? %>
20
+ <div class="<%= actions_classes %>">
21
+ <%= actions %>
22
+ </div>
23
+ <% end %>
24
+ </header>