hakumi_components 0.1.16.pre → 0.1.17.pre

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +169 -23
  3. data/app/assets/javascripts/hakumi_components.js +12 -12
  4. data/app/assets/stylesheets/hakumi_components.css +1 -1
  5. data/app/components/hakumi/alert/component.html.erb +12 -8
  6. data/app/components/hakumi/alert/component.rb +18 -62
  7. data/app/components/hakumi/base_component.rb +13 -0
  8. data/app/components/hakumi/card/component.html.erb +14 -22
  9. data/app/components/hakumi/card/component.rb +38 -31
  10. data/app/components/hakumi/checkbox/component.html.erb +39 -21
  11. data/app/components/hakumi/checkbox/component.rb +12 -2
  12. data/app/components/hakumi/collapse/component.html.erb +2 -2
  13. data/app/components/hakumi/collapse/component.rb +1 -1
  14. data/app/components/hakumi/collapse/panel/component.rb +9 -0
  15. data/app/components/hakumi/color_picker/component.rb +0 -4
  16. data/app/components/hakumi/drawer/component.html.erb +7 -7
  17. data/app/components/hakumi/drawer/component.rb +12 -19
  18. data/app/components/hakumi/input/component.rb +0 -2
  19. data/app/components/hakumi/input/text_area/component.rb +0 -2
  20. data/app/components/hakumi/input_number/component.rb +3 -4
  21. data/app/components/hakumi/mentions/component.rb +0 -1
  22. data/app/components/hakumi/modal/component.html.erb +40 -0
  23. data/app/components/hakumi/modal/component.rb +24 -102
  24. data/app/components/hakumi/modal/confirm/component.html.erb +23 -0
  25. data/app/components/hakumi/modal/confirm/component.rb +23 -41
  26. data/app/components/hakumi/modal/error/component.rb +12 -11
  27. data/app/components/hakumi/modal/info/component.rb +12 -11
  28. data/app/components/hakumi/modal/success/component.rb +12 -11
  29. data/app/components/hakumi/modal/warning/component.rb +15 -10
  30. data/app/components/hakumi/popconfirm/component.html.erb +25 -25
  31. data/app/components/hakumi/popconfirm/component.rb +11 -27
  32. data/app/components/hakumi/rate/component.rb +0 -1
  33. data/app/components/hakumi/segmented/component.rb +0 -4
  34. data/app/components/hakumi/slider/component.rb +2 -6
  35. data/app/components/hakumi/statistic/component.rb +0 -4
  36. data/app/components/hakumi/switch/component.html.erb +4 -0
  37. data/app/components/hakumi/switch/component.rb +1 -2
  38. data/app/components/hakumi/table/component.rb +3 -229
  39. data/app/components/hakumi/table/concerns/columns.rb +1 -1
  40. data/app/components/hakumi/table/concerns/editable.rb +121 -0
  41. data/app/components/hakumi/table/concerns/ellipsis.rb +63 -0
  42. data/app/components/hakumi/table/concerns/fixed_columns.rb +87 -0
  43. data/app/components/hakumi/transfer/component.rb +0 -4
  44. data/app/controllers/{hakumi_components → hakumi}/components_controller.rb +2 -2
  45. data/app/form_builders/hakumi/form_builder.rb +217 -175
  46. data/app/helpers/hakumi/form_helper.rb +39 -0
  47. data/app/javascript/hakumi_components/controllers/base/registry_controller.js +83 -3
  48. data/app/javascript/hakumi_components/controllers/hakumi/affix_controller.js +0 -23
  49. data/app/javascript/hakumi_components/controllers/hakumi/alert_controller.js +2 -1
  50. data/app/javascript/hakumi_components/controllers/hakumi/button_controller.js +0 -7
  51. data/app/javascript/hakumi_components/controllers/hakumi/calendar_controller.js +0 -2
  52. data/app/javascript/hakumi_components/controllers/hakumi/color_picker_controller.js +1 -6
  53. data/app/javascript/hakumi_components/controllers/hakumi/date_picker_controller.js +28 -34
  54. data/app/javascript/hakumi_components/controllers/hakumi/drawer_controller.js +2 -1
  55. data/app/javascript/hakumi_components/controllers/hakumi/form_item_controller.js +9 -63
  56. data/app/javascript/hakumi_components/controllers/hakumi/mentions_controller.js +4 -11
  57. data/app/javascript/hakumi_components/controllers/hakumi/message_controller.js +1 -1
  58. data/app/javascript/hakumi_components/controllers/hakumi/modal_controller.js +4 -20
  59. data/app/javascript/hakumi_components/controllers/hakumi/notification_controller.js +1 -1
  60. data/app/javascript/hakumi_components/controllers/hakumi/popconfirm_controller.js +33 -27
  61. data/app/javascript/hakumi_components/controllers/hakumi/popover_controller.js +2 -23
  62. data/app/javascript/hakumi_components/controllers/hakumi/qr_code_controller.js +0 -20
  63. data/app/javascript/hakumi_components/controllers/hakumi/segmented_controller.js +0 -2
  64. data/app/javascript/hakumi_components/controllers/hakumi/spin_controller.js +1 -19
  65. data/app/javascript/hakumi_components/controllers/hakumi/statistic_controller.js +0 -2
  66. data/app/javascript/hakumi_components/controllers/hakumi/table_controller.js +48 -74
  67. data/app/javascript/hakumi_components/controllers/hakumi/tag_controller.js +15 -14
  68. data/app/javascript/hakumi_components/controllers/hakumi/tag_group_controller.js +14 -13
  69. data/app/javascript/hakumi_components/controllers/hakumi/theme_controller.js +24 -1
  70. data/app/javascript/hakumi_components/controllers/hakumi/time_picker_controller.js +3 -7
  71. data/app/javascript/hakumi_components/controllers/hakumi/timeline_controller.js +0 -16
  72. data/app/javascript/hakumi_components/controllers/hakumi/transfer_controller.js +2 -2
  73. data/app/javascript/hakumi_components/controllers/hakumi/tree_controller.js +0 -2
  74. data/app/javascript/hakumi_components/controllers/hakumi/tree_select_controller.js +3 -3
  75. data/app/javascript/hakumi_components/controllers/hakumi/upload_controller.js +12 -26
  76. data/app/javascript/hakumi_components/core/persistence.js +3 -3
  77. data/app/javascript/hakumi_components/core/render_component.js +3 -1
  78. data/app/javascript/lib/validation_manager.js +101 -0
  79. data/app/javascript/stylesheets/_theme-tokens.scss +2 -1
  80. data/app/javascript/stylesheets/components/_modal.scss +13 -0
  81. data/app/services/{hakumi_components → hakumi}/component_handler.rb +1 -1
  82. data/app/services/hakumi/icon/loader.rb +2 -2
  83. data/app/services/hakumi/illustrations/loader.rb +3 -3
  84. data/app/views/hakumi/_drawer.html.erb +21 -0
  85. data/app/views/hakumi/_modal.html.erb +18 -0
  86. data/lib/hakumi_components/documentation.rb +127 -0
  87. data/lib/hakumi_components/engine.rb +13 -4
  88. data/lib/hakumi_components/rails/attribute_introspection.rb +1 -1
  89. data/lib/hakumi_components/rails/validation_introspection.rb +5 -5
  90. data/lib/hakumi_components/rails/validation_mapper.rb +484 -0
  91. data/lib/hakumi_components/rails.rb +2 -1
  92. data/lib/hakumi_components/version.rb +2 -2
  93. data/lib/hakumi_components.rb +3 -1
  94. data/lib/tasks/coverage.rake +37 -0
  95. data/sig/hakumi/base_component.rbs +5 -0
  96. data/sig/hakumi/checkbox/component.rbs +10 -0
  97. data/sig/hakumi/color_picker/component.rbs +0 -1
  98. data/sig/hakumi/form_builder.rbs +9 -1
  99. data/sig/{hakumi_components → hakumi}/rails/attribute_introspection.rbs +1 -1
  100. data/sig/{hakumi_components → hakumi}/rails/validation_introspection.rbs +1 -1
  101. data/sig/hakumi/rails/validation_mapper.rbs +53 -0
  102. data/sig/{hakumi_components → hakumi}/rails.rbs +1 -1
  103. data/sig/hakumi/segmented/component.rbs +0 -1
  104. data/sig/hakumi/slider/component.rbs +0 -1
  105. data/sig/hakumi/statistic/component.rbs +0 -2
  106. data/sig/hakumi/table/component.rbs +3 -4
  107. data/sig/hakumi/table/concerns/columns.rbs +2 -1
  108. data/sig/hakumi/table/concerns/editable.rbs +40 -0
  109. data/sig/hakumi/table/concerns/ellipsis.rbs +27 -0
  110. data/sig/hakumi/table/concerns/fixed_columns.rbs +33 -0
  111. data/sig/hakumi/transfer/component.rbs +0 -1
  112. data/sig/{hakumi_components.rbs → hakumi.rbs} +20 -3
  113. data/sig/rails/active_model/validations/comparison_validator.rbs +6 -0
  114. metadata +44 -29
  115. data/app/views/hakumi_components/_drawer.html.erb +0 -3
  116. data/app/views/hakumi_components/_modal.html.erb +0 -3
  117. /data/app/views/{hakumi_components → hakumi}/_admin_panel.html.erb +0 -0
  118. /data/app/views/{hakumi_components → hakumi}/_affix.html.erb +0 -0
  119. /data/app/views/{hakumi_components → hakumi}/_alert.html.erb +0 -0
  120. /data/app/views/{hakumi_components → hakumi}/_confirm.html.erb +0 -0
  121. /data/app/views/{hakumi_components → hakumi}/_message.html.erb +0 -0
  122. /data/app/views/{hakumi_components → hakumi}/_notification.html.erb +0 -0
  123. /data/app/views/{hakumi_components → hakumi}/_popconfirm.html.erb +0 -0
  124. /data/app/views/{hakumi_components → hakumi}/_popover.html.erb +0 -0
  125. /data/app/views/{hakumi_components → hakumi}/_qr_code.html.erb +0 -0
  126. /data/app/views/{hakumi_components → hakumi}/_result.html.erb +0 -0
  127. /data/app/views/{hakumi_components → hakumi}/_segmented.html.erb +0 -0
  128. /data/app/views/{hakumi_components → hakumi}/_skeleton.html.erb +0 -0
  129. /data/app/views/{hakumi_components → hakumi}/_spin.html.erb +0 -0
  130. /data/app/views/{hakumi_components → hakumi}/_statistic.html.erb +0 -0
  131. /data/app/views/{hakumi_components → hakumi}/_table.html.erb +0 -0
  132. /data/app/views/{hakumi_components → hakumi}/_tag.html.erb +0 -0
  133. /data/app/views/{hakumi_components → hakumi}/_timeline.html.erb +0 -0
  134. /data/app/views/{hakumi_components → hakumi}/_tree.html.erb +0 -0
@@ -1,7 +1,11 @@
1
1
  <div <%= tag.attributes(wrapper_attributes) %>>
2
2
  <% if show_icon? %>
3
3
  <span class="hakumi-alert-icon">
4
- <%= icon_element %>
4
+ <% if icon? %>
5
+ <%= icon %>
6
+ <% else %>
7
+ <%= default_icon_element %>
8
+ <% end %>
5
9
  </span>
6
10
  <% end %>
7
11
 
@@ -9,19 +13,19 @@
9
13
  <% if message_content.present? %>
10
14
  <div class="hakumi-alert-message" data-hakumi--alert-target="message"><%= message_content %></div>
11
15
  <% end %>
12
- <% if description_present? %>
13
- <div class="hakumi-alert-description" data-hakumi--alert-target="description"><%= description_content %></div>
16
+ <% if description? %>
17
+ <div class="hakumi-alert-description" data-hakumi--alert-target="description"><%= description %></div>
14
18
  <% end %>
15
19
  </div>
16
20
 
17
- <% if action_present? %>
18
- <div class="hakumi-alert-action"><%= action_content %></div>
21
+ <% if action? %>
22
+ <div class="hakumi-alert-action"><%= action %></div>
19
23
  <% end %>
20
24
 
21
25
  <% if @closable %>
22
- <button type="button" class="<%= close_button_classes %>" data-action="click->hakumi--alert#close" aria-label="Close">
23
- <% if @close_text.present? %>
24
- <span><%= @close_text %></span>
26
+ <button type="button" class="hakumi-alert-close-icon" data-hakumi-action="close" aria-label="Close">
27
+ <% if close_icon? %>
28
+ <%= close_icon %>
25
29
  <% else %>
26
30
  <%= render Hakumi::Icon::Component.new(name: :close) %>
27
31
  <% end %>
@@ -5,41 +5,24 @@ module Hakumi
5
5
  class Component < Hakumi::BaseComponent
6
6
  TYPES = [ :success, :info, :warning, :error ].freeze
7
7
 
8
- # Extracts locals from controller params for dynamic rendering
9
- # @param params [ActionController::Parameters]
10
- # @return [Hash] Arguments for initialize
11
- def self.extract_controller_locals(params)
12
- {
13
- type: params[:type]&.to_sym,
14
- message: params[:message].presence || "Generated Alert content",
15
- description: params[:description],
16
- show_icon: ActiveModel::Type::Boolean.new.cast(params[:show_icon]),
17
- closable: ActiveModel::Type::Boolean.new.cast(params[:closable]),
18
- banner: ActiveModel::Type::Boolean.new.cast(params[:banner])
19
- }
20
- end
8
+ # Slots for content
9
+ renders_one :icon
10
+ renders_one :message
11
+ renders_one :description
12
+ renders_one :action
13
+ renders_one :close_icon
21
14
 
22
15
  def initialize(
23
16
  type: nil,
24
- message: nil,
25
- description: nil,
26
17
  show_icon: nil,
27
- icon: nil,
28
18
  closable: false,
29
- close_text: nil,
30
19
  banner: false,
31
- action: nil,
32
20
  **html_options
33
21
  )
34
22
  @type = type || :info
35
- @message = message
36
- @description = description
37
23
  @show_icon = show_icon.nil? ? banner : show_icon
38
- @icon = icon
39
- @closable = closable || close_text.present?
40
- @close_text = close_text
24
+ @closable = closable
41
25
  @banner = banner
42
- @action = action
43
26
  @html_options = html_options
44
27
 
45
28
  validate_props!
@@ -54,7 +37,7 @@ module Hakumi
54
37
  def wrapper_classes
55
38
  classes = [ "hakumi-alert" ]
56
39
  classes << "hakumi-alert-#{@type}" if @type
57
- classes << "hakumi-alert-with-description" if description_present?
40
+ classes << "hakumi-alert-with-description" if description?
58
41
  classes << "hakumi-alert-with-icon" if show_icon?
59
42
  classes << "hakumi-alert-no-icon" unless show_icon?
60
43
  classes << "hakumi-alert-closable" if @closable
@@ -72,57 +55,30 @@ module Hakumi
72
55
  )
73
56
  end
74
57
 
58
+ # Content resolution: message slot > block content
75
59
  def message_content
76
- content.presence || @message
77
- end
78
-
79
- def description_present?
80
- @description.present?
81
- end
82
-
83
- def description_content
84
- render_value(@description)
85
- end
86
-
87
- def action_present?
88
- @action.present?
89
- end
90
-
91
- def action_content
92
- render_value(@action)
60
+ message? ? message : content
93
61
  end
94
62
 
95
63
  def show_icon?
96
64
  @show_icon
97
65
  end
98
66
 
99
- def icon_element
67
+ def default_icon_element
100
68
  return nil unless show_icon?
101
69
 
102
- icon = @icon || default_icon
103
- return icon unless icon.is_a?(String) || icon.is_a?(Symbol)
104
-
105
- render Hakumi::Icon::Component.new(name: icon)
70
+ icon_name = default_icon_name
71
+ render Hakumi::Icon::Component.new(name: icon_name)
106
72
  end
107
73
 
108
- def default_icon
74
+ def default_icon_name
109
75
  case @type
110
- when :success
111
- :check_circle
112
- when :warning
113
- :exclamation_circle
114
- when :error
115
- :close_circle
116
- else
117
- :info_circle
76
+ when :success then :check_circle
77
+ when :warning then :exclamation_circle
78
+ when :error then :close_circle
79
+ else :info_circle
118
80
  end
119
81
  end
120
-
121
- def close_button_classes
122
- classes = [ "hakumi-alert-close-icon" ]
123
- classes << "hakumi-alert-close-text" if @close_text.present?
124
- classes.join(" ")
125
- end
126
82
  end
127
83
  end
128
84
  end
@@ -154,6 +154,19 @@ module Hakumi
154
154
  string.empty? ? nil : string
155
155
  end
156
156
 
157
+ # Cast a value to boolean using ActiveModel::Type::Boolean
158
+ # @param value [Object] Value to cast
159
+ # @return [Boolean] Casted boolean value
160
+ #
161
+ # Example:
162
+ # cast_boolean("true") # => true
163
+ # cast_boolean("1") # => true
164
+ # cast_boolean(0) # => false
165
+ # cast_boolean(nil) # => nil
166
+ def cast_boolean(value)
167
+ ActiveModel::Type::Boolean.new.cast(value)
168
+ end
169
+
157
170
  # Build inline style string from array or hash
158
171
  # @param styles [Array<String>, Hash] Styles as array of CSS strings or hash of property => value
159
172
  # @return [String, nil] Combined style string
@@ -1,36 +1,28 @@
1
- <% wrapper_tag = @href.present? ? :a : :div %>
2
- <% wrapper_attrs = @html_options.except(:class).dup %>
3
- <% wrapper_attrs[:class] = classes %>
4
- <% if @href.present? %>
5
- <% wrapper_attrs[:href] = @href %>
6
- <% wrapper_attrs[:target] = @target if @target %>
7
- <% wrapper_attrs[:class] = [wrapper_attrs[:class], "hakumi-card-link-wrapper"].compact.join(" ") %>
8
- <% end %>
9
- <%= content_tag(wrapper_tag, **wrapper_attrs) do %>
10
- <% if @head_style.present? || @title.present? || @extra.present? %>
11
- <div class="<%= head_classes %>" style="<%= @head_style %>">
1
+ <%= content_tag(wrapper_tag, **wrapper_attributes) do %>
2
+ <% if render_header? %>
3
+ <div class="hakumi-card-head">
12
4
  <div class="hakumi-card-head-wrapper">
13
- <% if @title.present? %>
5
+ <% if header? %>
14
6
  <div class="hakumi-card-head-title">
15
- <%= render_title %>
7
+ <%= header %>
16
8
  </div>
17
9
  <% end %>
18
- <% if @extra.present? %>
10
+ <% if extra? %>
19
11
  <div class="hakumi-card-extra">
20
- <%= render_extra %>
12
+ <%= extra %>
21
13
  </div>
22
14
  <% end %>
23
15
  </div>
24
16
  </div>
25
17
  <% end %>
26
18
 
27
- <% if @cover.present? %>
19
+ <% if cover? %>
28
20
  <div class="hakumi-card-cover">
29
- <%= @cover %>
21
+ <%= cover %>
30
22
  </div>
31
23
  <% end %>
32
24
 
33
- <div class="<%= body_classes %>" style="<%= @body_style %>">
25
+ <div class="hakumi-card-body">
34
26
  <% if @loading %>
35
27
  <div class="hakumi-card-loading-content">
36
28
  <div class="hakumi-row" style="margin-bottom: 16px;">
@@ -49,14 +41,14 @@
49
41
  </div>
50
42
  </div>
51
43
  <% else %>
52
- <%= content %>
44
+ <%= body_content %>
53
45
  <% end %>
54
46
  </div>
55
47
 
56
- <% if @actions.any? %>
48
+ <% if actions.any? %>
57
49
  <ul class="hakumi-card-actions">
58
- <% @actions.each_with_index do |action, index| %>
59
- <li style="width: <%= 100.0 / @actions.length %>%">
50
+ <% actions.each do |action| %>
51
+ <li style="width: <%= 100.0 / actions.length %>%">
60
52
  <span><%= action %></span>
61
53
  </li>
62
54
  <% end %>
@@ -5,43 +5,44 @@ module Hakumi
5
5
  # Hakumi Card component
6
6
  # A container for displaying content in a box.
7
7
  #
8
- # @example Basic usage
9
- # <%= render Hakumi::Card::Component.new(title: "Card Title", extra: "More") do %>
8
+ # @example Basic usage with slots
9
+ # <%= render Hakumi::Card::Component.new do |card| %>
10
+ # <% card.with_header { "Card Title" } %>
11
+ # <% card.with_extra { "More" } %>
10
12
  # Card content
11
13
  # <% end %>
12
14
  #
13
- # @example With actions and cover
14
- # <%= render Hakumi::Card::Component.new(
15
- # title: "Card Title",
16
- # cover: image_tag("example.png"),
17
- # actions: [link_to("Edit"), link_to("Delete")]
18
- # ) do %>
15
+ # @example With cover and actions
16
+ # <%= render Hakumi::Card::Component.new do |card| %>
17
+ # <% card.with_cover { image_tag("example.png") } %>
18
+ # <% card.with_header { "Card Title" } %>
19
19
  # Card content
20
+ # <% card.with_action { link_to "Edit", edit_path } %>
21
+ # <% card.with_action { link_to "Delete", "#" } %>
20
22
  # <% end %>
21
23
  class Component < Hakumi::BaseComponent
24
+ # Slots for content
25
+ renders_one :header
26
+ renders_one :cover
27
+ renders_one :body
28
+ renders_one :extra
29
+ renders_many :actions
30
+
22
31
  def initialize(
23
- title: nil,
24
- extra: nil,
25
32
  bordered: true,
26
33
  hoverable: false,
27
34
  loading: false,
28
35
  size: :default, # :default, :small
29
36
  type: nil, # :inner, nil
30
- actions: [],
31
- cover: nil,
32
37
  href: nil,
33
38
  target: nil,
34
39
  **html_options
35
40
  )
36
- @title = title
37
- @extra = extra
38
41
  @bordered = bordered
39
42
  @hoverable = hoverable
40
43
  @loading = loading
41
44
  @size = size
42
45
  @type = type
43
- @actions = actions
44
- @cover = cover
45
46
  @href = href
46
47
  @target = target
47
48
  @html_options = html_options
@@ -49,6 +50,22 @@ module Hakumi
49
50
 
50
51
  private
51
52
 
53
+ def wrapper_tag
54
+ @href.present? ? :a : :div
55
+ end
56
+
57
+ def wrapper_attributes
58
+ attrs = @html_options.dup
59
+ attrs[:class] = classes
60
+
61
+ if @href.present?
62
+ attrs[:href] = @href
63
+ attrs[:target] = @target if @target
64
+ end
65
+
66
+ attrs
67
+ end
68
+
52
69
  def classes
53
70
  class_names(
54
71
  "card",
@@ -58,29 +75,19 @@ module Hakumi
58
75
  small: @size == :small,
59
76
  "type-inner": @type == :inner,
60
77
  loading: @loading,
61
- "contain-grid": false,
62
78
  link: @href.present?
63
79
  },
64
80
  @html_options[:class]
65
81
  )
66
82
  end
67
83
 
68
- def head_classes
69
- "hakumi-card-head"
70
- end
71
-
72
- def body_classes
73
- "hakumi-card-body"
74
- end
75
-
76
- def render_title
77
- return @title if @title.is_a?(String)
78
- @title
84
+ def render_header?
85
+ header? || extra?
79
86
  end
80
87
 
81
- def render_extra
82
- return @extra if @extra.is_a?(String)
83
- @extra
88
+ # Content resolution: body slot > block content
89
+ def body_content
90
+ body? ? body : content
84
91
  end
85
92
  end
86
93
  end
@@ -1,21 +1,39 @@
1
- <label id="<%= @wrapper_id %>" class="<%= wrapper_classes %>" data-controller="<%= data_controllers %>" data-hakumi--checkbox-checked-value="<%= @checked %>" data-hakumi--checkbox-indeterminate-value="<%= @indeterminate %>">
2
- <span class="<%= checkbox_classes %>" data-hakumi--checkbox-target="checkbox">
3
- <%= check_box_tag(
4
- @name,
5
- @value,
6
- @checked,
7
- id: @id,
8
- disabled: @disabled,
9
- class: "hakumi-checkbox-input",
10
- data: {
11
- hakumi__checkbox_target: "input",
12
- action: "change->hakumi--checkbox#toggle"
13
- }.merge(@html_options[:data] || {}),
14
- **@html_options.except(:class, :data, :style)
15
- ) %>
16
- <span class="hakumi-checkbox-inner"></span>
17
- </span>
18
- <% if @label.present? || content.present? %>
19
- <span><%= @label || content %></span>
20
- <% end %>
21
- </label>
1
+ <% checkbox_control = capture do %>
2
+ <label id="<%= @wrapper_id %>" class="<%= wrapper_classes %>" data-controller="<%= data_controllers %>" data-hakumi--checkbox-checked-value="<%= @checked %>" data-hakumi--checkbox-indeterminate-value="<%= @indeterminate %>">
3
+ <span class="<%= checkbox_classes %>" data-hakumi--checkbox-target="checkbox">
4
+ <%= check_box_tag(
5
+ @name,
6
+ @value,
7
+ @checked,
8
+ id: @id,
9
+ disabled: @disabled,
10
+ class: "hakumi-checkbox-input",
11
+ data: {
12
+ hakumi__checkbox_target: "input",
13
+ action: "change->hakumi--checkbox#toggle"
14
+ }.merge(@html_options[:data] || {}),
15
+ **@html_options.except(:id, :class, :data, :style)
16
+ ) %>
17
+ <span class="hakumi-checkbox-inner"></span>
18
+ </span>
19
+ <% if @label.present? || content.present? %>
20
+ <span><%= @label || content %></span>
21
+ <% end %>
22
+ </label>
23
+ <% end %>
24
+
25
+ <% if standalone? %>
26
+ <%= checkbox_control %>
27
+ <% else %>
28
+ <div <%= tag.attributes(form_item_attributes).to_s.html_safe %>>
29
+ <%= render_label(field_id: @id) %>
30
+
31
+ <div class="hakumi-form-item-control">
32
+ <div class="hakumi-form-item-control-input">
33
+ <%= checkbox_control %>
34
+ </div>
35
+
36
+ <%= render_explain %>
37
+ </div>
38
+ </div>
39
+ <% end %>
@@ -3,6 +3,8 @@
3
3
  module Hakumi
4
4
  module Checkbox
5
5
  class Component < Hakumi::BaseComponent
6
+ include Hakumi::Concerns::FormField
7
+
6
8
  def initialize(
7
9
  checked: false,
8
10
  disabled: false,
@@ -12,17 +14,25 @@ module Hakumi
12
14
  id: nil,
13
15
  auto_focus: false,
14
16
  label: nil,
17
+ caption: nil,
18
+ standalone: true,
19
+ required: false,
20
+ errors: [],
15
21
  **html_options
16
22
  )
17
23
  @checked = checked
18
24
  @disabled = disabled
19
25
  @indeterminate = indeterminate
20
- @value = value
26
+ @value = value || "1"
21
27
  @name = name
22
28
  @id = id || generate_id("checkbox")
23
29
  @auto_focus = auto_focus
24
30
  @label = label
25
- @wrapper_id = html_options.delete(:id) || generate_id("hakumi-checkbox", length: 6)
31
+ @caption = caption
32
+ @standalone = standalone
33
+ @required = required
34
+ @errors = Array(errors)
35
+ @wrapper_id = html_options.delete(:id) || generate_id("hakumi-checkbox")
26
36
  @html_options = html_options
27
37
  end
28
38
 
@@ -12,9 +12,9 @@
12
12
  </span>
13
13
  <% end %>
14
14
  <span class="hakumi-collapse-header-text">
15
- <%= item.header || item.key %>
15
+ <%= item.header_content %>
16
16
  </span>
17
- <% if item.extra.present? %>
17
+ <% if item.extra? %>
18
18
  <div class="hakumi-collapse-extra">
19
19
  <%= item.extra %>
20
20
  </div>
@@ -87,7 +87,7 @@ module Hakumi
87
87
  def stimulus_values
88
88
  {
89
89
  accordion: @accordion,
90
- activeKeys: initial_active_keys,
90
+ active_keys: initial_active_keys,
91
91
  collapsible: (@collapsible&.to_s),
92
92
  bordered: @bordered,
93
93
  ghost: @ghost
@@ -50,6 +50,15 @@ module Hakumi
50
50
  }
51
51
  merge_attributes(base, @html_options.except(:class))
52
52
  end
53
+
54
+ # Content resolution for header: header prop > key fallback
55
+ def header_content
56
+ @header || @key
57
+ end
58
+
59
+ def extra?
60
+ @extra.present?
61
+ end
53
62
  end
54
63
  end
55
64
  end
@@ -164,10 +164,6 @@ module Hakumi
164
164
  [ { label: "Presets", colors: Array(presets).compact } ]
165
165
  end
166
166
  end
167
-
168
- def cast_boolean(value)
169
- ActiveModel::Type::Boolean.new.cast(value)
170
- end
171
167
  end
172
168
  end
173
169
  end
@@ -17,26 +17,26 @@
17
17
  <div class="hakumi-drawer-header">
18
18
  <div class="hakumi-drawer-header-title">
19
19
  <% if @closable %>
20
- <button type="button" class="hakumi-drawer-close" aria-label="Close" data-action="click->hakumi--drawer#close">
20
+ <button type="button" class="hakumi-drawer-close" aria-label="Close" data-hakumi-action="close">
21
21
  <span class="hakumi-drawer-close-x">
22
22
  <%= render Hakumi::Icon::Component.new(name: :close) %>
23
23
  </span>
24
24
  </button>
25
25
  <% end %>
26
- <% if @title.present? %>
27
- <div class="hakumi-drawer-title"><%= @title %></div>
26
+ <% if header? %>
27
+ <div class="hakumi-drawer-title"><%= header %></div>
28
28
  <% end %>
29
29
  </div>
30
- <% if @extra.present? %>
31
- <div class="hakumi-drawer-extra"><%= @extra %></div>
30
+ <% if extra? %>
31
+ <div class="hakumi-drawer-extra"><%= extra %></div>
32
32
  <% end %>
33
33
  </div>
34
34
  <% end %>
35
35
  <div class="hakumi-drawer-body" data-hakumi--drawer-target="body">
36
- <%= content %>
36
+ <%= body_content %>
37
37
  </div>
38
38
  <% if render_footer? %>
39
- <div class="hakumi-drawer-footer"><%= @footer %></div>
39
+ <div class="hakumi-drawer-footer"><%= footer %></div>
40
40
  <% end %>
41
41
  </div>
42
42
  </div>
@@ -9,21 +9,14 @@ module Hakumi
9
9
  large: 736
10
10
  }.freeze
11
11
 
12
- # Extracts locals from controller params for dynamic rendering
13
- # @param params [ActionController::Parameters]
14
- # @return [Hash] Arguments for initialize
15
- def self.extract_controller_locals(params)
16
- {
17
- title: params[:title].presence || "Drawer",
18
- message: params[:message].presence || params[:body].presence || "Generated drawer content.",
19
- open: ActiveModel::Type::Boolean.new.cast(params[:open]),
20
- placement: (params[:placement].presence || :right).to_sym
21
- }
22
- end
12
+ # Slots for content
13
+ renders_one :header
14
+ renders_one :body
15
+ renders_one :footer
16
+ renders_one :extra
23
17
 
24
18
  def initialize(
25
19
  open: false,
26
- title: nil,
27
20
  placement: :right,
28
21
  size: :default,
29
22
  width: nil,
@@ -32,13 +25,10 @@ module Hakumi
32
25
  mask: true,
33
26
  mask_closable: true,
34
27
  keyboard: true,
35
- footer: nil,
36
- extra: nil,
37
28
  destroy_on_close: false,
38
29
  **html_attributes
39
30
  )
40
31
  @open = open
41
- @title = title
42
32
  @placement = placement
43
33
  @size = size
44
34
  @width = width
@@ -47,8 +37,6 @@ module Hakumi
47
37
  @mask = mask
48
38
  @mask_closable = mask_closable
49
39
  @keyboard = keyboard
50
- @footer = footer
51
- @extra = extra
52
40
  @destroy_on_close = destroy_on_close
53
41
  @html_attributes = html_attributes
54
42
 
@@ -91,11 +79,16 @@ module Hakumi
91
79
  end
92
80
 
93
81
  def render_header?
94
- @title.present? || @closable || @extra.present?
82
+ header? || @closable || extra?
95
83
  end
96
84
 
97
85
  def render_footer?
98
- @footer.present?
86
+ footer?
87
+ end
88
+
89
+ # Content resolution: body slot > block content
90
+ def body_content
91
+ body? ? body : content
99
92
  end
100
93
 
101
94
  private
@@ -115,8 +115,6 @@ module Hakumi
115
115
  class: has_affix? ? nil : input_classes,
116
116
  disabled: @disabled ? true : nil,
117
117
  readonly: @readonly ? true : nil,
118
- required: @required ? true : nil,
119
- maxlength: @maxlength,
120
118
  "aria-invalid": has_error? ? "true" : nil,
121
119
  "aria-describedby": describedby_ids,
122
120
  data: { action: "input->hakumi--input#handleInput" }
@@ -106,8 +106,6 @@ module Hakumi
106
106
  placeholder: @placeholder,
107
107
  disabled: @disabled ? true : nil,
108
108
  readonly: @readonly ? true : nil,
109
- required: @required ? true : nil,
110
- maxlength: @maxlength,
111
109
  "aria-invalid": has_error? ? "true" : nil,
112
110
  "aria-describedby": describedby_ids,
113
111
  data: { action: "input->hakumi--input-textarea#handleInput" }
@@ -134,8 +134,8 @@ module Hakumi
134
134
  }.compact
135
135
  },
136
136
  {
137
- id: @html_attrs[:wrapper_id],
138
- data: @html_attrs[:wrapper_data]
137
+ id: @html_attrs[:id],
138
+ data: @html_attrs[:data]
139
139
  }.compact
140
140
  )
141
141
  end
@@ -156,7 +156,6 @@ module Hakumi
156
156
  class: input_classes,
157
157
  disabled: @disabled ? true : nil,
158
158
  readonly: @readonly ? true : nil,
159
- required: @required ? true : nil,
160
159
  autocomplete: "off",
161
160
  role: "spinbutton",
162
161
  "aria-invalid": has_error? ? "true" : nil,
@@ -169,7 +168,7 @@ module Hakumi
169
168
  hakumi__input_number_target: "input"
170
169
  }
171
170
  }.compact,
172
- @html_attrs.except(:wrapper_class, :wrapper_id, :wrapper_data)
171
+ @html_attrs.except(:id, :wrapper_class)
173
172
  )
174
173
  end
175
174