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,165 @@
1
+ module Daisy
2
+ module DataInput
3
+ # A specialized input component that combines a text input with a calendar
4
+ # picker. The calendar appears in a popover when the input is focused or
5
+ # clicked.
6
+ #
7
+ # @part popover The container for the calendar popover.
8
+ #
9
+ # @slot input The text input component.
10
+ # @slot calendar The calendar component that appears in the popover.
11
+ #
12
+ # @loco_example Basic Usage
13
+ # = daisy_cally_input(name: "event_date", value: Date.today)
14
+ #
15
+ # @loco_example With custom calendar and input components
16
+ # = daisy_cally_input(name: "event_date") do |c|
17
+ # = c.with_calendar(css: "custom-calendar")
18
+ # = c.with_input(css: "custom-input")
19
+ class CallyInputComponent < LocoMotion::BaseComponent
20
+ # A specialized text input component for the CallyInput that handles
21
+ # popover targeting and data attributes needed for the Stimulus
22
+ # controller.
23
+ class CallyTextInputComponent < Daisy::DataInput::TextInputComponent
24
+ # Sets up the HTML attributes needed for the text input before
25
+ # rendering. Configures the popover target, ID, name, and data
26
+ # attributes required for the Stimulus controller to function properly.
27
+ #
28
+ # @return [void]
29
+ def before_render
30
+ parent_config = loco_parent.config
31
+
32
+ # Since we're pulling options from the parent, we have to do a little
33
+ # more work to ensure we set it up correctly.
34
+ @start = config_option(:start, parent_config.options[:start])
35
+ @end = config_option(:end, parent_config.options[:end])
36
+ @floating_placeholder = config_option(:floating_placeholder, parent_config.options[:floating_placeholder])
37
+ @floating = config_option(:floating, parent_config.options[:floating] || @floating_placeholder)
38
+ @placeholder = config_option(:placeholder, parent_config.options[:placeholder] || @floating_placeholder)
39
+
40
+ super
41
+
42
+ add_html(:component, {
43
+ popovertarget: loco_parent.popover_id,
44
+ id: @id || loco_parent.input_id,
45
+ name: @name || loco_parent.name,
46
+ value: @value || parent_config.options[:value],
47
+ placeholder: @placeholder,
48
+ style: "anchor-name:--#{loco_parent.anchor}",
49
+ data: {
50
+ "loco-cally-input-target": "input"
51
+ }
52
+ })
53
+ end
54
+
55
+ def call
56
+ render_parent_to_string
57
+ end
58
+ end
59
+
60
+ # A specialized calendar component for the CallyInput that handles the calendar
61
+ # display and interaction within a popover.
62
+ class CallyCalendarComponent < Daisy::DataInput::CallyComponent
63
+ # Sets up the HTML attributes needed for the calendar before rendering.
64
+ # Configures the ID, value, and data attributes required for the Stimulus controller.
65
+ #
66
+ # @return [void]
67
+ def before_render
68
+ super
69
+
70
+ add_html(:component, {
71
+ id: @id || loco_parent.calendar_id,
72
+ value: @value || loco_parent.value,
73
+ data: {
74
+ "loco-cally-input-target": "calendar"
75
+ }
76
+ })
77
+ end
78
+
79
+ def call
80
+ render_parent_to_string
81
+ end
82
+ end
83
+
84
+ include ViewComponent::SlotableDefault
85
+ include LocoMotion::Concerns::LabelableComponent
86
+
87
+ define_parts :popover
88
+
89
+ renders_one :calendar, CallyCalendarComponent
90
+ renders_one :input, CallyTextInputComponent
91
+
92
+ attr_reader :id, :name, :value, :calendar_id, :input_id, :popover_id, :anchor, :auto_scroll_padding
93
+
94
+ # Initializes a new CallyInputComponent.
95
+ #
96
+ # @param [Hash] kws The options hash
97
+ # @option kws [String] :id A unique identifier for the input (default: auto-generated)
98
+ # @option kws [String] :name The name attribute for the input field
99
+ # @option kws [Date, String] :value The initial value of the input (default: nil)
100
+ # @option kws [Integer] :auto_scroll_padding The padding to use when scrolling the calendar into view (default: 100)
101
+ # @option kws [String] :css Additional CSS classes for the component
102
+ def initialize(**kws)
103
+ super(**kws)
104
+
105
+ @id = config_option(:id, SecureRandom.uuid)
106
+ @name = config_option(:name)
107
+ @value = config_option(:value)
108
+ @auto_scroll_padding = config_option(:auto_scroll_padding, 100)
109
+
110
+ # Input ID should match our ID
111
+ @input_id = @id
112
+
113
+ # Other IDs / options are generated
114
+ @calendar_id = "#{@id}-calendar"
115
+ @popover_id = "#{@id}-popover"
116
+ @anchor = "#{@id}-anchor"
117
+ end
118
+
119
+ # Sets up the component before rendering.
120
+ # Calls the parent's before_render and then runs the component setup.
121
+ #
122
+ # @return [void]
123
+ def before_render
124
+ super
125
+
126
+ setup_component
127
+ end
128
+
129
+ # Provides a default calendar component instance.
130
+ # This is used when no custom calendar component is provided.
131
+ #
132
+ # @return [CallyCalendarComponent] A new instance of the default calendar component
133
+ def default_calendar
134
+ CallyCalendarComponent.new
135
+ end
136
+
137
+ # Provides a default input component instance.
138
+ # This is used when no custom input component is provided.
139
+ #
140
+ # @return [CallyTextInputComponent] A new instance of the default input component
141
+ def default_input
142
+ CallyTextInputComponent.new
143
+ end
144
+
145
+ private
146
+
147
+ # Sets up the component's HTML structure and attributes.
148
+ # Configures the Stimulus controller and popover attributes.
149
+ #
150
+ # @return [void]
151
+ # @private
152
+ def setup_component
153
+ # Ensure we attach the Stimulus controller
154
+ add_stimulus_controller(:component, "loco-cally-input")
155
+
156
+ # Add relevant popover part HTML
157
+ add_html(:popover, { id: @popover_id, popover: "auto", style: "position-anchor:--#{@anchor}" })
158
+ add_html(:popover, { data: { "loco-cally-input-target": "popover", "auto-scroll-padding": @auto_scroll_padding } })
159
+
160
+ # Note that we NEED the dropdown class so that the anchor positioning works properly
161
+ add_css(:popover, "where:dropdown where:bg-base-100 where:rounded where:shadow-lg")
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,235 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ /**
4
+ * Controller for handling calendar input interactions.
5
+ * Manages the popover calendar UI and synchronization between input and calendar elements.
6
+ */
7
+ export default class extends Controller {
8
+ static targets = ["calendar", "input", "popover"]
9
+
10
+ /**
11
+ * Initializes the controller and sets up event listeners.
12
+ * Binds all methods to ensure proper 'this' context.
13
+ *
14
+ * @returns {void}
15
+ */
16
+ connect() {
17
+
18
+ // Save the bound functions so we can remove them later
19
+ this.boundUpdateInput = this.updateInput.bind(this)
20
+ this.boundUpdateCalendar = this.updateCalendar.bind(this)
21
+ this.boundOpenCalendar = this.openCalendar.bind(this)
22
+ this.boundCloseCalendar = this.closeCalendar.bind(this)
23
+ this.boundHandleKeydown = this.handleKeydown.bind(this)
24
+ this.boundHandleToggle = this.handleToggle.bind(this)
25
+
26
+ // Bind all of our functions
27
+ this.calendarTarget.addEventListener("change", this.boundUpdateInput)
28
+ this.calendarTarget.addEventListener("blur", this.boundCloseCalendar)
29
+
30
+ this.inputTarget.addEventListener("change", this.boundUpdateCalendar)
31
+ this.inputTarget.addEventListener("keyup", this.boundUpdateCalendar)
32
+ this.inputTarget.addEventListener("click", this.boundOpenCalendar)
33
+ this.inputTarget.addEventListener("keydown", this.boundHandleKeydown)
34
+
35
+ this.popoverTarget.addEventListener("toggle", this.boundHandleToggle)
36
+ }
37
+
38
+ /**
39
+ * Cleans up event listeners when the controller is disconnected.
40
+ * Prevents memory leaks by removing all bound event handlers.
41
+ *
42
+ * @returns {void}
43
+ */
44
+ disconnect() {
45
+ this.calendarTarget.removeEventListener("change", this.boundUpdateInput)
46
+ this.calendarTarget.removeEventListener("blur", this.boundCloseCalendar)
47
+
48
+ this.inputTarget.removeEventListener("change", this.boundUpdateCalendar)
49
+ this.inputTarget.removeEventListener("keyup", this.boundUpdateCalendar)
50
+ this.inputTarget.removeEventListener("keydown", this.boundHandleKeydown)
51
+ this.inputTarget.removeEventListener("click", this.boundOpenCalendar)
52
+ this.popoverTarget.removeEventListener("toggle", this.boundHandleToggle)
53
+ }
54
+
55
+ /**
56
+ * Opens the calendar popover.
57
+ *
58
+ * @returns {void}
59
+ */
60
+ openCalendar() {
61
+ // Open the popover
62
+ this.popoverTarget.togglePopover(true)
63
+ }
64
+
65
+ /**
66
+ * Closes the calendar popover if the blur event is not related to calendar elements.
67
+ *
68
+ * @param {FocusEvent} event - The blur event object.
69
+ *
70
+ * @returns {void}
71
+ */
72
+ closeCalendar(event) {
73
+ // Don't close if we're still in the calendar elements
74
+ if (event?.relatedTarget && this.calendarTarget.contains(event.relatedTarget)) {
75
+ return
76
+ }
77
+
78
+ this.popoverTarget.togglePopover(false)
79
+ }
80
+
81
+ /**
82
+ * Opens the calendar if it is closed, or closes it if it is open.
83
+ *
84
+ * @returns {void}
85
+ */
86
+ toggleCalendar() {
87
+ this.popoverTarget.togglePopover()
88
+ }
89
+
90
+ /**
91
+ * Handles the popover toggle event.
92
+ * - When opening: Sets a zero-width space in empty inputs to prevent floating label flicker
93
+ * - When closing: Clears the single-space input to ensure proper floating label behavior
94
+ *
95
+ * @param {Event} event - The toggle event object with newState property
96
+ * @returns {void}
97
+ */
98
+ handleToggle(event) {
99
+ const hasFloatingLabel = this.inputTarget.parentElement.classList.contains("floating-label")
100
+ const isOpen = event.newState == 'open'
101
+
102
+ if (isOpen) {
103
+ // Make sure the calendar is visible
104
+ this.scrollCalendarIntoView()
105
+ }
106
+
107
+ if (hasFloatingLabel) {
108
+ const ZERO_WIDTH_SPACE = '\u200B'
109
+
110
+ if (isOpen) {
111
+ // Set the input to a zero-width space if it is empty to prevent a floating label
112
+ // flicker when the calendar loses focus while setting the date
113
+ if (this.inputTarget.value == null || this.inputTarget.value === '') {
114
+ this.inputTarget.value = ZERO_WIDTH_SPACE;
115
+ }
116
+ } else {
117
+ // Unset the zero-width space input on close so the floating label works again
118
+ if (this.inputTarget.value === ZERO_WIDTH_SPACE) {
119
+ this.inputTarget.value = null
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Ensures the calendar popover is visible within the viewport.
127
+ *
128
+ * If the popover extends beyond the viewport edges, scrolls the window to
129
+ * bring it into view adding any data-auto-scroll-padding.
130
+ *
131
+ * @returns {void}
132
+ */
133
+ scrollCalendarIntoView() {
134
+ const rect = this.popoverTarget.getBoundingClientRect()
135
+ const autoScrollPadding = parseInt(this.popoverTarget.dataset.autoScrollPadding, 10)
136
+ const padding = isNaN(autoScrollPadding) ? 0 : autoScrollPadding
137
+
138
+ if (rect.bottom > window.innerHeight) {
139
+ window.scrollBy({ top: rect.bottom - window.innerHeight + padding, behavior: 'smooth' })
140
+ } else if (rect.top < 0) {
141
+ window.scrollBy({ top: rect.top - padding, behavior: 'smooth' })
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Updates the input value from the calendar and closes the popover.
147
+ * Synchronizes the input field with the selected date.
148
+ *
149
+ * @returns {void}
150
+ */
151
+ updateInput() {
152
+ // Update the input value and close the popover
153
+ this.inputTarget.value = this.calendarTarget.value
154
+ this.closeCalendar()
155
+ }
156
+
157
+ /**
158
+ * Handles keyboard navigation for the calendar input.
159
+ * - SPACE: Toggles the popover
160
+ * - ENTER: Closes an open popover
161
+ * - ARROW DOWN / ARROW UP: Opens the popover and focuses the calendar
162
+ *
163
+ * @param {KeyboardEvent} event - The keyboard event object.
164
+ *
165
+ * @returns {void}
166
+ */
167
+ handleKeydown(event) {
168
+ const hasModifierKeys = event.ctrlKey || event.shiftKey || event.altKey || event.metaKey;
169
+ const isOpen = this.popoverTarget.matches(':popover-open')
170
+
171
+ //
172
+ // SPACE - Toggles the popover.
173
+ //
174
+ if (event.key === ' ' || event.code === 'Space') {
175
+ event.preventDefault()
176
+
177
+ this.toggleCalendar()
178
+ }
179
+
180
+ //
181
+ // ENTER - Closes an open popover
182
+ //
183
+ else if (isOpen && (event.key === 'Enter' || event.code === 'Enter')) {
184
+ // Stop the enter key from triggering the form submission
185
+ event.preventDefault()
186
+
187
+ this.closeCalendar()
188
+ }
189
+
190
+ //
191
+ // ARROW DOWN / ARROW UP - Opens the popover and focuses the calendar
192
+ //
193
+ else if ((event.key === 'ArrowDown' || event.code === 'ArrowUp') && !hasModifierKeys) {
194
+ event.preventDefault()
195
+
196
+ // If the calendar is not already open, open it
197
+ if (!isOpen) {
198
+ this.openCalendar()
199
+ }
200
+
201
+ // Focus the calendar element
202
+ this.calendarTarget.focus()
203
+
204
+ // Forward the keydown event to the calendar
205
+ const forwardedEvent = new KeyboardEvent('keydown', {
206
+ key: event.key,
207
+ code: event.code,
208
+ bubbles: true,
209
+ cancelable: true,
210
+ })
211
+
212
+ // Dispatch the forwarded event to the calendar
213
+ this.calendarTarget.dispatchEvent(forwardedEvent)
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Updates the calendar's value based on the input field.
219
+ * Only updates if the input contains a valid ISO 8601 date.
220
+ *
221
+ * @returns {void}
222
+ */
223
+ updateCalendar() {
224
+ // Only update the calendar if we have a full / valid ISO 8601 date
225
+ let newDate = this.inputTarget.value
226
+
227
+ if (newDate.length !== 10 || new Date(newDate).toString() === "Invalid Date") {
228
+ return
229
+ }
230
+
231
+ // Set the calendar value and focused-date attributes
232
+ this.calendarTarget.value = newDate
233
+ this.calendarTarget.setAttribute('focused-date', newDate)
234
+ }
235
+ }
@@ -0,0 +1,20 @@
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
+ = part(:component)
11
+
12
+ - if has_end_label?
13
+ - if end?
14
+ = self.send(:end)
15
+ - else
16
+ = part(:end) do
17
+ = @end
18
+
19
+ - else
20
+ = part(:component)
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # The Checkbox component renders a DaisyUI styled checkbox input.
5
+ # It can be used standalone or with a form builder, and supports various styling
6
+ # options including toggle mode for switch-like appearance.
7
+ #
8
+ # @part label_wrapper The wrapper element for labels (when using
9
+ # start/end/floating labels).
10
+ # @part start The element that contains the start label (appears before the
11
+ # checkbox).
12
+ # @part end The element that contains the end label (appears after the checkbox).
13
+ #
14
+ # @slot start Custom content for the start label.
15
+ # @slot end Custom content for the end label.
16
+ #
17
+ # @loco_example Basic Usage
18
+ # = daisy_checkbox(name: "accept", id: "accept")
19
+ #
20
+ # @loco_example Checked Checkbox
21
+ # = daisy_checkbox(name: "accept", id: "accept", checked: true)
22
+ #
23
+ # @loco_example Toggle Checkbox
24
+ # = daisy_checkbox(name: "accept", id: "accept", toggle: true)
25
+ #
26
+ # @loco_example Disabled Checkbox
27
+ # = daisy_checkbox(name: "accept", id: "accept", disabled: true)
28
+ #
29
+ # @loco_example With End Label (common for checkboxes)
30
+ # = daisy_checkbox(name: "terms", id: "terms", end: "I agree to the terms and conditions")
31
+ #
32
+ class Daisy::DataInput::CheckboxComponent < LocoMotion::BaseComponent
33
+ include LocoMotion::Concerns::LabelableComponent
34
+
35
+ attr_reader :name, :id, :value, :checked, :toggle, :disabled, :required
36
+
37
+ #
38
+ # Instantiate a new Checkbox component.
39
+ #
40
+ # @param kws [Hash] The keyword arguments for the component.
41
+ #
42
+ # @option kws name [String] The name attribute for the checkbox input.
43
+ #
44
+ # @option kws id [String] The ID attribute for the checkbox input.
45
+ #
46
+ # @option kws value [String] The value attribute for the checkbox input.
47
+ # Defaults to "1".
48
+ #
49
+ # @option kws checked [Boolean] Whether the checkbox is checked. Defaults to
50
+ # false.
51
+ #
52
+ # @option kws toggle [Boolean] Whether the checkbox should be styled as a
53
+ # toggle switch. Defaults to false.
54
+ #
55
+ # @option kws disabled [Boolean] Whether the checkbox is disabled. Defaults to
56
+ # false.
57
+ #
58
+ # @option kws required [Boolean] Whether the checkbox is required for form
59
+ # validation. Defaults to false.
60
+ #
61
+ def initialize(**kws)
62
+ super
63
+
64
+ @name = config_option(:name)
65
+ @id = config_option(:id)
66
+ @value = config_option(:value, "1")
67
+ @checked = config_option(:checked, false)
68
+ @toggle = config_option(:toggle, false)
69
+ @disabled = config_option(:disabled, false)
70
+ @required = config_option(:required, false)
71
+ end
72
+
73
+ #
74
+ # Calls the {setup_component} method before rendering the component.
75
+ #
76
+ def before_render
77
+ super
78
+
79
+ setup_labels
80
+ setup_component
81
+ end
82
+
83
+ def setup_labels
84
+ add_css(:label_wrapper, "label") if has_any_label?
85
+ end
86
+
87
+ #
88
+ # Sets up the component by configuring the tag name, CSS classes, and HTML
89
+ # attributes.
90
+ #
91
+ def setup_component
92
+ set_tag_name(:component, :input)
93
+
94
+ add_css(:component, @toggle ? "toggle" : "checkbox")
95
+
96
+ add_html(:component, {
97
+ type: "checkbox",
98
+ name: @name,
99
+ id: @id,
100
+ value: @value,
101
+ checked: @checked,
102
+ disabled: @disabled,
103
+ required: @required
104
+ })
105
+ end
106
+ end
@@ -0,0 +1,8 @@
1
+ = part(:component) do
2
+ - if legend?
3
+ = legend
4
+ - elsif @simple_legend
5
+ = part(:legend) { @simple_legend }
6
+
7
+ -# Render the main content passed to the component block
8
+ = content
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Daisy
4
+ module DataInput
5
+ # Renders a fieldset element, optionally with a legend, to group
6
+ # related form controls or content.
7
+ #
8
+ # @loco_example Basic fieldset
9
+ # = daisy_fieldset do
10
+ # Content inside fieldset
11
+ #
12
+ # @loco_example Fieldset with legend slot
13
+ # = daisy_fieldset do |fieldset|
14
+ # - fieldset.with_legend { "My Legend" }
15
+ #
16
+ # Content inside fieldset
17
+ #
18
+ # @loco_example Fieldset with legend argument
19
+ # = daisy_fieldset(legend: "My Legend") do
20
+ # Content inside fieldset
21
+ #
22
+ class FieldsetComponent < LocoMotion::BaseComponent
23
+ self.component_name = :fieldset
24
+
25
+ define_parts :legend
26
+
27
+ # The legend (title) for the fieldset. Renders a `<legend>` tag.
28
+ # Can be provided as a slot or via the `legend` argument.
29
+ renders_one :legend, LocoMotion::BasicComponent.build(tag_name: :legend, css: "fieldset-legend")
30
+
31
+ # @param legend [String] Optional simple text for the legend.
32
+ # Ignored if the `legend` slot is used.
33
+ def initialize(legend: nil, **kws)
34
+ @simple_legend = legend
35
+
36
+ super(**kws)
37
+ end
38
+
39
+ def before_render
40
+ setup_component
41
+
42
+ super
43
+ end
44
+
45
+ private
46
+
47
+ # Sets up default tags and classes for the component and its parts.
48
+ def setup_component
49
+ set_tag_name(:component, :fieldset)
50
+ set_tag_name(:legend, :legend)
51
+
52
+ add_css(:legend, "fieldset-legend")
53
+ add_css(:component, "fieldset")
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # The FileInput component renders a DaisyUI styled file input.
5
+ # It can be used standalone or with a form builder, and supports
6
+ # various styling options including different sizes and variants.
7
+ #
8
+ # @note File inputs have a border by default. Use `file-input-ghost` to remove
9
+ # the border.
10
+ #
11
+ # @loco_example Basic Usage
12
+ # = daisy_file_input(name: "document", id: "document")
13
+ #
14
+ # @loco_example With Accept Attribute
15
+ # = daisy_file_input(name: "image", id: "image", accept: "image/*")
16
+ #
17
+ # @loco_example With Multiple Files
18
+ # = daisy_file_input(name: "documents[]", id: "documents", multiple: true)
19
+ #
20
+ # @loco_example Ghost Style (No Border)
21
+ # = daisy_file_input(name: "document", id: "document", css: "file-input-ghost")
22
+ #
23
+ # @loco_example Disabled File Input
24
+ # = daisy_file_input(name: "document", id: "document", disabled: true)
25
+ #
26
+ class Daisy::DataInput::FileInputComponent < LocoMotion::BaseComponent
27
+ attr_reader :name, :id, :accept, :multiple, :disabled, :required
28
+
29
+ #
30
+ # Instantiate a new FileInput component.
31
+ #
32
+ # @param kws [Hash] The keyword arguments for the component.
33
+ #
34
+ # @option kws name [String] The name attribute for the file input.
35
+ #
36
+ # @option kws id [String] The ID attribute for the file input.
37
+ #
38
+ # @option kws accept [String] The accept attribute for the file input,
39
+ # specifying allowed file types.
40
+ #
41
+ # @option kws multiple [Boolean] Whether the file input allows multiple file
42
+ # selection. Defaults to false.
43
+ #
44
+ # @option kws disabled [Boolean] Whether the file input is disabled. Defaults to
45
+ # false.
46
+ #
47
+ # @option kws required [Boolean] Whether the file input is required for form
48
+ # validation. Defaults to false.
49
+ #
50
+ def initialize(**kws)
51
+ super
52
+
53
+ @name = config_option(:name)
54
+ @id = config_option(:id)
55
+ @accept = config_option(:accept, nil)
56
+ @multiple = config_option(:multiple, false)
57
+ @disabled = config_option(:disabled, false)
58
+ @required = config_option(:required, false)
59
+ end
60
+
61
+ #
62
+ # Calls the {setup_component} method before rendering the component.
63
+ #
64
+ def before_render
65
+ setup_component
66
+ end
67
+
68
+ #
69
+ # Sets up the component by configuring the tag name, CSS classes, and HTML
70
+ # attributes. Sets the tag to input with type 'file' and adds the 'file-input'
71
+ # CSS class.
72
+ #
73
+ # This configures the name, id, accept attribute, multiple state, disabled state,
74
+ # and required state of the file input.
75
+ #
76
+ def setup_component
77
+ set_tag_name(:component, :input)
78
+
79
+ add_css(:component, "file-input")
80
+
81
+ add_html(:component, {
82
+ type: "file",
83
+ name: @name,
84
+ id: @id,
85
+ accept: @accept,
86
+ multiple: @multiple,
87
+ disabled: @disabled,
88
+ required: @required
89
+ })
90
+ end
91
+
92
+ #
93
+ # Renders the component inline with no additional whitespace.
94
+ #
95
+ def call
96
+ part(:component)
97
+ end
98
+ end
@@ -0,0 +1,3 @@
1
+ = part(:component) do
2
+ = reset_button if reset_button?
3
+ = render_filter_options