loco_motion-rails 0.0.8 → 0.5.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +62 -14
  3. data/app/components/daisy/actions/button_component.html.haml +2 -2
  4. data/app/components/daisy/actions/button_component.rb +98 -59
  5. data/app/components/daisy/actions/dropdown_component.html.haml +1 -2
  6. data/app/components/daisy/actions/dropdown_component.rb +7 -10
  7. data/app/components/daisy/actions/modal_component.html.haml +10 -8
  8. data/app/components/daisy/actions/modal_component.rb +6 -6
  9. data/app/components/daisy/actions/swap_component.rb +13 -9
  10. data/app/components/daisy/actions/theme_controller.js +113 -0
  11. data/app/components/daisy/actions/theme_controller_component.rb +58 -17
  12. data/app/components/daisy/actions/theme_preview_component.html.haml +5 -0
  13. data/app/components/daisy/actions/theme_preview_component.rb +68 -0
  14. data/app/components/daisy/data_display/accordion_component.html.haml +0 -1
  15. data/app/components/daisy/data_display/accordion_component.rb +10 -3
  16. data/app/components/daisy/data_display/avatar_component.html.haml +1 -1
  17. data/app/components/daisy/data_display/avatar_component.rb +17 -7
  18. data/app/components/daisy/data_display/badge_component.rb +122 -4
  19. data/app/components/daisy/data_display/card_component.html.haml +1 -1
  20. data/app/components/daisy/data_display/card_component.rb +20 -6
  21. data/app/components/daisy/data_display/chat_component.rb +2 -2
  22. data/app/components/daisy/data_display/collapse_component.rb +9 -5
  23. data/app/components/daisy/data_display/countdown_component.rb +15 -5
  24. data/app/components/daisy/data_display/figure_component.rb +8 -3
  25. data/app/components/daisy/data_display/kbd_component.rb +13 -4
  26. data/app/components/daisy/data_display/list_component.html.haml +5 -0
  27. data/app/components/daisy/data_display/list_component.rb +82 -0
  28. data/app/components/daisy/data_display/list_item_component.rb +39 -0
  29. data/app/components/daisy/data_display/stat_component.html.haml +5 -6
  30. data/app/components/daisy/data_display/stat_component.rb +21 -8
  31. data/app/components/daisy/data_display/status_component.rb +47 -0
  32. data/app/components/daisy/data_display/timeline_component.rb +1 -1
  33. data/app/components/daisy/data_input/cally_component.html.haml +14 -0
  34. data/app/components/daisy/data_input/cally_component.rb +182 -0
  35. data/app/components/daisy/data_input/cally_input_component.html.haml +5 -0
  36. data/app/components/daisy/data_input/cally_input_component.rb +165 -0
  37. data/app/components/daisy/data_input/cally_input_controller.js +235 -0
  38. data/app/components/daisy/data_input/checkbox_component.html.haml +20 -0
  39. data/app/components/daisy/data_input/checkbox_component.rb +106 -0
  40. data/app/components/daisy/data_input/fieldset_component.html.haml +8 -0
  41. data/app/components/daisy/data_input/fieldset_component.rb +57 -0
  42. data/app/components/daisy/data_input/file_input_component.rb +98 -0
  43. data/app/components/daisy/data_input/filter_component.html.haml +3 -0
  44. data/app/components/daisy/data_input/filter_component.rb +221 -0
  45. data/app/components/daisy/data_input/label_component.rb +84 -0
  46. data/app/components/daisy/data_input/radio_button_component.rb +87 -0
  47. data/app/components/daisy/data_input/range_component.rb +95 -0
  48. data/app/components/daisy/data_input/rating_component.html.haml +11 -0
  49. data/app/components/daisy/data_input/rating_component.rb +139 -0
  50. data/app/components/daisy/data_input/select_component.html.haml +27 -0
  51. data/app/components/daisy/data_input/select_component.rb +320 -0
  52. data/app/components/daisy/data_input/text_area_component.rb +127 -0
  53. data/app/components/daisy/data_input/text_input_component.html.haml +27 -0
  54. data/app/components/daisy/data_input/text_input_component.rb +142 -0
  55. data/app/components/daisy/data_input/toggle_component.rb +48 -0
  56. data/app/components/daisy/feedback/alert_component.html.haml +1 -1
  57. data/app/components/daisy/feedback/alert_component.rb +86 -2
  58. data/app/components/daisy/feedback/loading_component.rb +10 -3
  59. data/app/components/daisy/feedback/skeleton_component.rb +1 -1
  60. data/app/components/daisy/layout/divider_component.rb +4 -2
  61. data/app/components/daisy/layout/drawer_component.html.haml +0 -1
  62. data/app/components/daisy/layout/footer_component.rb +6 -6
  63. data/app/components/daisy/mockup/device_component.rb +15 -18
  64. data/app/components/daisy/navigation/breadcrumbs_component.html.haml +0 -1
  65. data/app/components/daisy/navigation/breadcrumbs_component.rb +84 -9
  66. data/app/components/daisy/navigation/dock_component.rb +146 -0
  67. data/app/components/daisy/navigation/link_component.rb +18 -9
  68. data/app/components/daisy/navigation/menu_component.rb +15 -9
  69. data/app/components/daisy/navigation/navbar_component.html.haml +1 -1
  70. data/app/components/daisy/navigation/navbar_component.rb +2 -13
  71. data/app/components/daisy/navigation/steps_component.rb +6 -6
  72. data/app/components/daisy/navigation/tabs_component.html.haml +0 -1
  73. data/app/components/daisy/navigation/tabs_component.rb +26 -16
  74. data/app/components/hero/icon_component.rb +15 -5
  75. data/app/helpers/daisy/form_builder_helper.rb +186 -0
  76. data/app/views/examples/daisy/data_input/filters.html.haml +62 -0
  77. data/lib/daisy.rb +5 -0
  78. data/lib/hero.rb +1 -1
  79. data/lib/loco_motion/base_component.rb +53 -3
  80. data/lib/loco_motion/component_config.rb +1 -0
  81. data/lib/loco_motion/concerns/iconable_component.rb +134 -0
  82. data/lib/loco_motion/concerns/labelable_component.rb +142 -0
  83. data/lib/loco_motion/concerns/linkable_component.rb +40 -0
  84. data/lib/loco_motion/concerns/tippable_component.rb +25 -10
  85. data/lib/loco_motion/engine.rb +6 -0
  86. data/lib/loco_motion/helpers.rb +38 -17
  87. data/lib/loco_motion/patches/view_component/slot_loco_parent_patch.rb +37 -0
  88. data/lib/loco_motion/patches/view_component/slotable_default_patch.rb +21 -0
  89. data/lib/loco_motion/version.rb +1 -1
  90. data/lib/loco_motion.rb +12 -2
  91. metadata +93 -21
  92. data/app/components/daisy/actions/theme_controller_component.html.haml +0 -5
  93. data/app/components/daisy/layout/artboard_component.rb +0 -59
  94. data/app/components/daisy/navigation/bottom_nav_component.rb +0 -138
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # The Filter component is a group of radio buttons where choosing one option
5
+ # hides the others and shows a reset button.
6
+ #
7
+ # @loco_example Basic Usage
8
+ # = daisy_filter(name: "frameworks", options: ["Svelte", "Vue", "React"])
9
+ #
10
+ # @loco_example Using Hash Options
11
+ # = daisy_filter(name: "languages", options: [
12
+ # { label: "Ruby", value: "ruby" },
13
+ # { label: "JavaScript", value: "js" },
14
+ # { label: "Python", value: "py" }
15
+ # ])
16
+ #
17
+ # @loco_example Custom Styling
18
+ # = daisy_filter(name: "priorities", css: "items-center") do |f|
19
+ # - f.with_reset_button(css: "btn-accent btn-sm rounded-full")
20
+ # - f.with_option(label: "Low", css: "btn-outline btn-success")
21
+ # - f.with_option(label: "Medium", css: "btn-outline btn-warning")
22
+ # - f.with_option(label: "High", css: "btn-outline btn-error")
23
+ #
24
+ # @loco_example Within Form Builder
25
+ # = form_with(model: @post) do |form|
26
+ # = form.daisy_filter(:category, options: ["News", "Tech", "Sports"])
27
+ # = form.submit "Save", class: "btn btn-primary mt-4"
28
+ #
29
+ # @loco_example Form Builder With Block
30
+ # = form_with(model: @user) do |form|
31
+ # = form.daisy_filter(:role) do |f|
32
+ # - f.with_option(label: "Admin", value: "admin")
33
+ # - f.with_option(label: "Editor", value: "editor")
34
+ # - f.with_option(label: "Viewer", value: "viewer")
35
+ # = form.submit "Update", class: "btn btn-primary mt-4"
36
+ #
37
+ module Daisy
38
+ module DataInput
39
+ class FilterComponent < LocoMotion::BaseComponent
40
+ class FilterOptionComponent < Daisy::DataInput::RadioButtonComponent
41
+ #
42
+ # Initialize a new filter option component.
43
+ #
44
+ # @param kws [Hash] The keyword arguments for the component.
45
+ #
46
+ # @option kws label [String] The aria-label for the radio button.
47
+ #
48
+ def initialize(**kws)
49
+ super(**kws)
50
+
51
+ @label = config_option(:label)
52
+ @index = config_option(:index)
53
+ @skip_styling = true
54
+ end
55
+
56
+ #
57
+ # Setup the component before rendering.
58
+ #
59
+ def before_render
60
+ # Make sure to pull the default name from the parent
61
+ @name = config_option(:name, loco_parent.name)
62
+ @id = config_option(:id, "#{loco_parent.id}_#{@index}")
63
+
64
+ # Call the parent setup first
65
+ super
66
+
67
+ # Add btn class for styling
68
+ add_css(:component, "btn")
69
+ add_html(:component, { id: @id })
70
+
71
+ # Add aria-label if specified
72
+ if @label.present?
73
+ add_html(:component, { "aria-label": @label })
74
+ end
75
+ end
76
+ end
77
+
78
+ class FilterResetComponent < Daisy::DataInput::RadioButtonComponent
79
+ #
80
+ # Initialize a new filter reset component.
81
+ #
82
+ # @param kws [Hash] The keyword arguments for the component.
83
+ #
84
+ # @option kws disabled [Boolean] Whether the reset button is disabled. Defaults to false.
85
+ #
86
+ def initialize(**kws)
87
+ super(**kws)
88
+
89
+ @skip_styling = true
90
+ end
91
+
92
+ #
93
+ # Setup the component before rendering.
94
+ #
95
+ def before_render
96
+ # Make sure to pull the default name from the parent
97
+ @name = config_option(:name, loco_parent.name)
98
+ @id = config_option(:id, "#{loco_parent.id}_reset")
99
+
100
+ # Call parent setup first
101
+ super
102
+
103
+ # Add square styling
104
+ add_css(:component, "where:btn where:btn-square filter-reset")
105
+
106
+ # Set type to reset
107
+ add_html(:component, { type: "radio", id: @id })
108
+ end
109
+ end
110
+
111
+ include ViewComponent::SlotableDefault
112
+
113
+ renders_one :reset_button, FilterResetComponent
114
+ renders_many :options, FilterOptionComponent
115
+
116
+ attr_reader :name, :id
117
+
118
+ #
119
+ # Initialize a new filter component.
120
+ #
121
+ # @param kws [Hash] The keyword arguments for the component.
122
+ #
123
+ # @option kws name [String] Required name attribute for the radio button group.
124
+ #
125
+ # @option kws options [Array] An array of options to display in the filter.
126
+ # Can be an array of strings, symbols, or hashes with :label keys.
127
+ #
128
+ # @option kws value [String] The current value of the filter (for form integration).
129
+ #
130
+ def initialize(**kws)
131
+ super(**kws)
132
+
133
+ @name = config_option(:name)
134
+ @id = config_option(:id, SecureRandom.uuid)
135
+ @options_list = config_option(:options)
136
+ @value = config_option(:value)
137
+ end
138
+
139
+ #
140
+ # Setup the component before rendering.
141
+ #
142
+ def before_render
143
+ super
144
+
145
+ setup_component
146
+ end
147
+
148
+ def default_reset_button
149
+ FilterResetComponent.new(name: @name)
150
+ end
151
+
152
+ #
153
+ # Converts the options array into FilterOptionComponent instances.
154
+ # Handles both hash options (with label keys) and simple string/symbol options.
155
+ #
156
+ # @return [Array<FilterOptionComponent>] Array of option components or empty array if @options_list is nil.
157
+ #
158
+ def standard_options
159
+ return [] unless options_list
160
+
161
+ options_list.map.with_index do |option, index|
162
+ label = option.is_a?(Hash) ? option[:label] : option.to_s
163
+ value = option.is_a?(Hash) ? option[:value] : option
164
+
165
+ # Check if this option should be selected based on the component's value
166
+ checked = @value.present? && @value.to_s == value.to_s
167
+
168
+ Daisy::DataInput::FilterComponent::FilterOptionComponent.new(
169
+ loco_parent: component_ref,
170
+ name: @name,
171
+ label: label,
172
+ value: value,
173
+ checked: checked,
174
+ index: index.to_s # Ensure index is a string
175
+ )
176
+ end
177
+ end
178
+
179
+ #
180
+ # Renders the filter options based on the configuration.
181
+ # This method is used by the template to render options consistently.
182
+ #
183
+ # @return [String] The HTML for all options in the filter.
184
+ #
185
+ def render_filter_options
186
+ result = ""
187
+
188
+ if options?
189
+ options.each do |option|
190
+ result += render(option)
191
+ end
192
+ elsif standard_options.present?
193
+ standard_options.each do |option|
194
+ result += render(option)
195
+ end
196
+ end
197
+
198
+ result.html_safe
199
+ end
200
+
201
+ private
202
+
203
+ #
204
+ # Sets up the component by configuring the tag name, CSS classes, and HTML attributes.
205
+ #
206
+ def setup_component
207
+ # Add base component class
208
+ add_css(:component, "filter")
209
+ end
210
+
211
+ #
212
+ # Ensures the options list is always an array, even if a single option is provided.
213
+ #
214
+ # @return [Array] The list of options as an array.
215
+ #
216
+ def options_list
217
+ [@options_list].flatten.compact
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # The Label component renders a DaisyUI styled label for form inputs.
5
+ # It can be used with any form input component and provides visual styling
6
+ # consistent with other Daisy UI elements.
7
+ #
8
+ # @loco_example Basic Usage
9
+ # = daisy_label(for: "input_id") do
10
+ # My Label
11
+ #
12
+ # @loco_example With Label Title
13
+ # = daisy_label(for: "input_id", title: "My Label")
14
+ #
15
+ # @loco_example Required Label
16
+ # = daisy_label(for: "input_id", required: true) do
17
+ # My Required Label
18
+ #
19
+ class Daisy::DataInput::LabelComponent < LocoMotion::BaseComponent
20
+ attr_reader :for, :title, :required
21
+
22
+ #
23
+ # Instantiate a new Label component.
24
+ #
25
+ # @param kws [Hash] The keyword arguments for the component.
26
+ #
27
+ # @option kws for [String] The ID of the input element this label is for.
28
+ # This connects the label to its associated form control for accessibility.
29
+ #
30
+ # @option kws title [String] The text content of the label. If not provided,
31
+ # the content block will be used. If a content block is provided, it will
32
+ # take precedence over the title option.
33
+ #
34
+ # @option kws required [Boolean] Whether the label is for a required input.
35
+ # Defaults to false.
36
+ #
37
+ def initialize(title = nil, **kws)
38
+ super
39
+
40
+ @for = config_option(:for)
41
+ @title = config_option(:title, title)
42
+ @required = config_option(:required, false)
43
+ end
44
+
45
+ #
46
+ # Calls the {setup_component} method before rendering the component.
47
+ #
48
+ def before_render
49
+ setup_component
50
+ end
51
+
52
+ #
53
+ # Sets up the component by configuring the tag name, CSS classes, and HTML
54
+ # attributes. Sets the tag to 'label' and adds the 'label' CSS class.
55
+ #
56
+ # This configures the 'for' attribute to connect the label to its input and
57
+ # adds appropriate styling for required inputs when needed.
58
+ #
59
+ def setup_component
60
+ set_tag_name(:component, :label)
61
+
62
+ add_css(:component, "label")
63
+
64
+ add_html(:component, {
65
+ for: @for
66
+ })
67
+ end
68
+
69
+ #
70
+ # Renders the component with its content.
71
+ #
72
+ def call
73
+ part(:component) do
74
+ if content?
75
+ content
76
+ elsif @title
77
+ content_tag(:span, @title, class: "label-text")
78
+ else
79
+ # Fallback to empty content
80
+ ""
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # The Radio Button component renders a DaisyUI styled radio button input.
5
+ # It can be used standalone or with a form builder, and is ideal for creating
6
+ # option groups where users must select exactly one choice.
7
+ #
8
+ # @loco_example Basic Usage
9
+ # = daisy_radio(name: "option", id: "option1", value: "1")
10
+ #
11
+ # @loco_example Checked Radio Button
12
+ # = daisy_radio(name: "option", id: "option2", value: "2", checked: true)
13
+ #
14
+ # @loco_example Disabled Radio Button
15
+ # = daisy_radio(name: "option", id: "option3", value: "3", disabled: true)
16
+ #
17
+ class Daisy::DataInput::RadioButtonComponent < LocoMotion::BaseComponent
18
+ attr_reader :name, :id, :value, :checked, :disabled, :required
19
+
20
+ #
21
+ # Instantiate a new Radio Button component.
22
+ #
23
+ # @param kws [Hash] The keyword arguments for the component.
24
+ #
25
+ # @option kws name [String] The name attribute for the radio button input.
26
+ #
27
+ # @option kws id [String] The ID attribute for the radio button input.
28
+ #
29
+ # @option kws value [String] The value attribute for the radio button input.
30
+ #
31
+ # @option kws checked [Boolean] Whether the radio button is checked. Defaults to
32
+ # false.
33
+ #
34
+ # @option kws disabled [Boolean] Whether the radio button is disabled. Defaults to
35
+ # false.
36
+ #
37
+ # @option kws required [Boolean] Whether the radio button is required for form
38
+ # validation. Defaults to false.
39
+ #
40
+ def initialize(**kws)
41
+ super
42
+
43
+ @name = config_option(:name)
44
+ @id = config_option(:id)
45
+ @value = config_option(:value)
46
+ @checked = config_option(:checked, false)
47
+ @disabled = config_option(:disabled, false)
48
+ @required = config_option(:required, false)
49
+ end
50
+
51
+ #
52
+ # Calls the {setup_component} method before rendering the component.
53
+ #
54
+ def before_render
55
+ setup_component
56
+ end
57
+
58
+ #
59
+ # Sets up the component by configuring the tag name, CSS classes, and HTML
60
+ # attributes. Sets the tag to input with type 'radio' and adds the 'radio' CSS class.
61
+ #
62
+ # This configures the name, id, value, disabled state, required state, and
63
+ # checked state of the radio button.
64
+ #
65
+ def setup_component
66
+ set_tag_name(:component, :input)
67
+
68
+ add_css(:component, "radio") unless @skip_styling
69
+
70
+ add_html(:component, {
71
+ type: "radio",
72
+ name: @name,
73
+ id: @id,
74
+ value: @value,
75
+ checked: @checked,
76
+ disabled: @disabled,
77
+ required: @required
78
+ })
79
+ end
80
+
81
+ #
82
+ # Renders the component inline with no additional whitespace.
83
+ #
84
+ def call
85
+ part(:component)
86
+ end
87
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # The Range component renders a DaisyUI styled range input slider.
5
+ # It can be used standalone or with a form builder, and supports customization
6
+ # of min/max values, step increments, and color variants.
7
+ #
8
+ # @loco_example Basic Usage
9
+ # = daisy_range(name: "volume", id: "volume", min: 0, max: 100, value: 50)
10
+ #
11
+ # @loco_example Range with Steps
12
+ # = daisy_range(name: "step_range", id: "step_range", min: 0, max: 100, step: 25)
13
+ #
14
+ # @loco_example Range with Different Colors
15
+ # = daisy_range(name: "primary_range", id: "primary_range", css: "range-primary")
16
+ # = daisy_range(name: "error_range", id: "error_range", css: "range-error")
17
+ #
18
+ class Daisy::DataInput::RangeComponent < LocoMotion::BaseComponent
19
+ attr_reader :name, :id, :min, :max, :step, :value, :disabled, :required
20
+
21
+ #
22
+ # Instantiate a new Range component.
23
+ #
24
+ # @param kws [Hash] The keyword arguments for the component.
25
+ #
26
+ # @option kws name [String] The name attribute for the range input.
27
+ #
28
+ # @option kws id [String] The ID attribute for the range input.
29
+ #
30
+ # @option kws min [Integer] The minimum value (default: 0).
31
+ #
32
+ # @option kws max [Integer] The maximum value (default: 100).
33
+ #
34
+ # @option kws step [Integer] The step increment (default: 1).
35
+ #
36
+ # @option kws value [Integer] The current value (default: min).
37
+ #
38
+ # @option kws disabled [Boolean] Whether the range input is disabled. Defaults to
39
+ # false.
40
+ #
41
+ # @option kws required [Boolean] Whether the range input is required for form
42
+ # validation. Defaults to false.
43
+ #
44
+ def initialize(**kws)
45
+ super
46
+
47
+ @name = config_option(:name)
48
+ @id = config_option(:id)
49
+ @min = config_option(:min, 0)
50
+ @max = config_option(:max, 100)
51
+ @step = config_option(:step, 1)
52
+ @value = config_option(:value, @min)
53
+ @disabled = config_option(:disabled, false)
54
+ @required = config_option(:required, false)
55
+ end
56
+
57
+ #
58
+ # Calls the {setup_component} method before rendering the component.
59
+ #
60
+ def before_render
61
+ setup_component
62
+ end
63
+
64
+ #
65
+ # Sets up the component by configuring the tag name, CSS classes, and HTML
66
+ # attributes. Sets the tag to input with type 'range' and adds the 'range' CSS class.
67
+ #
68
+ # This configures the min, max, step values along with name, id, value, disabled state,
69
+ # and required state of the range input.
70
+ #
71
+ def setup_component
72
+ set_tag_name(:component, :input)
73
+
74
+ add_css(:component, "range")
75
+
76
+ add_html(:component, {
77
+ type: "range",
78
+ name: @name,
79
+ id: @id,
80
+ min: @min,
81
+ max: @max,
82
+ step: @step,
83
+ value: @value,
84
+ disabled: @disabled,
85
+ required: @required
86
+ })
87
+ end
88
+
89
+ #
90
+ # Renders the component inline with no additional whitespace.
91
+ #
92
+ def call
93
+ part(:component)
94
+ end
95
+ end
@@ -0,0 +1,11 @@
1
+ = part(:component) do
2
+ = part(:hidden_input)
3
+
4
+ - if items?
5
+ - items.each do |item|
6
+ = item
7
+ - elsif content?
8
+ = content
9
+ - elsif star_items.present?
10
+ - star_items.each do |item|
11
+ = render(item)
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # The Rating component renders a DaisyUI styled rating input using radio buttons.
5
+ # It can be used standalone or with a form builder, allowing users to select a
6
+ # rating from 1 to a configurable maximum value. Supports customization of size,
7
+ # colors, and initial value.
8
+ #
9
+ # @loco_example Basic Usage
10
+ # = daisy_rating(name: "product_rating", id: "product_rating")
11
+ #
12
+ # @loco_example Disabled Rating
13
+ # = daisy_rating(name: "disabled_rating", value: 3, disabled: true)
14
+ #
15
+ # @loco_example Rating with Different Colors
16
+ # = daisy_rating(name: "primary_rating", css: "[&>input]:bg-primary")
17
+ # = daisy_rating(name: "warning_rating", css: "[&>input]:bg-warning")
18
+ #
19
+ # @loco_example Rating with Different Sizes
20
+ # = daisy_rating(name: "small_rating", css: "rating-sm")
21
+ # = daisy_rating(name: "large_rating", css: "rating-lg")
22
+ #
23
+ class Daisy::DataInput::RatingComponent < LocoMotion::BaseComponent
24
+ #
25
+ # Inner component for rendering individual rating items as radio inputs.
26
+ #
27
+ class RatingItemComponent < LocoMotion::BasicComponent
28
+ #
29
+ # Sets up the component before rendering.
30
+ #
31
+ def before_render
32
+ set_tag_name(:component, :input)
33
+ add_html(:component, { name: loco_parent.name, type: "radio" })
34
+ end
35
+
36
+ #
37
+ # Renders the component with the appropriate attributes.
38
+ # Takes the name from the parent component and sets the type to radio.
39
+ #
40
+ # @return [String] The rendered HTML for the rating item.
41
+ #
42
+ def call
43
+ part(:component)
44
+ end
45
+ end
46
+
47
+ define_part :hidden_input
48
+
49
+ renders_many :items, RatingItemComponent
50
+
51
+ attr_reader :name, :value, :max, :disabled, :required, :id, :inputs_css, :inputs_html
52
+
53
+ #
54
+ # Instantiate a new Rating component.
55
+ #
56
+ # @param kws [Hash] The keyword arguments for the component.
57
+ #
58
+ # @option kws name [String] The name attribute for the radio inputs.
59
+ #
60
+ # @option kws value [Integer] The current rating value.
61
+ #
62
+ # @option kws max [Integer] The maximum rating value (default: 5).
63
+ #
64
+ # @option kws disabled [Boolean] Whether the rating is disabled. Defaults to
65
+ # false. When disabled, users cannot interact with the rating control.
66
+ #
67
+ # @option kws required [Boolean] Whether the rating input is required for form
68
+ # validation. Defaults to false.
69
+ #
70
+ # @option kws id [String] The ID for the first radio input.
71
+ #
72
+ # @option kws inputs_css [String] CSS classes to apply to each rating input.
73
+ #
74
+ # @option kws inputs_html [Hash] HTML attributes to apply to each rating input.
75
+ #
76
+ def initialize(**kws)
77
+ super
78
+
79
+ @name = config_option(:name)
80
+ @value = config_option(:value)
81
+ @max = config_option(:max, 5)
82
+ @disabled = config_option(:disabled, false)
83
+ @required = config_option(:required, false)
84
+ @id = config_option(:id)
85
+ @inputs_css = config_option(:inputs_css)
86
+ @inputs_html = config_option(:inputs_html, {})
87
+ end
88
+
89
+ #
90
+ # Calls the {setup_component} and {setup_hidden_input} methods before rendering
91
+ # the component. This prepares the rating container and creates a hidden input
92
+ # if needed.
93
+ #
94
+ def before_render
95
+ setup_component
96
+ setup_hidden_input
97
+ end
98
+
99
+ #
100
+ # Sets up the component by configuring the tag name and CSS classes.
101
+ #
102
+ def setup_component
103
+ set_tag_name(:component, :div)
104
+ add_css(:component, "rating")
105
+ add_html(:component, id: @id) if @id
106
+ end
107
+
108
+ #
109
+ # Sets up a hidden input so that the rating can start empty.
110
+ #
111
+ def setup_hidden_input
112
+ set_tag_name(:hidden_input, :input)
113
+ add_css(:hidden_input, "hidden")
114
+ add_html(:hidden_input, { type: "radio", name: @name, value: nil, checked: @value.blank? })
115
+ end
116
+
117
+ #
118
+ # Declares some default items to use if no custom items are provided.
119
+ #
120
+ def star_items
121
+ (1..@max).map do |rating|
122
+ input_attrs = {
123
+ loco_parent: component_ref,
124
+ css: ["where:mask where:mask-star", @inputs_css].compact.join(" "),
125
+ html: {
126
+ name: @name,
127
+ value: rating,
128
+ checked: @value == rating,
129
+ required: @required && rating == 1,
130
+ disabled: @disabled,
131
+ "aria-label": "#{rating} star",
132
+ **@inputs_html
133
+ }
134
+ }
135
+
136
+ RatingItemComponent.new(**input_attrs)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,27 @@
1
+ - if has_any_label?
2
+ = part(:label_wrapper) do
3
+ - if has_start_label?
4
+ - if start?
5
+ = start
6
+ - else
7
+ = part(:start) do
8
+ = @start
9
+
10
+ - if has_floating_label?
11
+ - if floating?
12
+ = floating
13
+ - else
14
+ = part(:floating) do
15
+ = @floating
16
+
17
+ = render_component
18
+
19
+ - if has_end_label?
20
+ - if end?
21
+ = self.send(:end)
22
+ - else
23
+ = part(:end) do
24
+ = @end
25
+
26
+ - else
27
+ = render_component