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.
- checksums.yaml +4 -4
- data/README.md +62 -14
- data/app/components/daisy/actions/button_component.html.haml +2 -2
- data/app/components/daisy/actions/button_component.rb +98 -59
- data/app/components/daisy/actions/dropdown_component.html.haml +1 -2
- data/app/components/daisy/actions/dropdown_component.rb +7 -10
- data/app/components/daisy/actions/modal_component.html.haml +10 -8
- data/app/components/daisy/actions/modal_component.rb +6 -6
- data/app/components/daisy/actions/swap_component.rb +13 -9
- data/app/components/daisy/actions/theme_controller.js +113 -0
- data/app/components/daisy/actions/theme_controller_component.rb +58 -17
- data/app/components/daisy/actions/theme_preview_component.html.haml +5 -0
- data/app/components/daisy/actions/theme_preview_component.rb +68 -0
- data/app/components/daisy/data_display/accordion_component.html.haml +0 -1
- data/app/components/daisy/data_display/accordion_component.rb +10 -3
- data/app/components/daisy/data_display/avatar_component.html.haml +1 -1
- data/app/components/daisy/data_display/avatar_component.rb +17 -7
- data/app/components/daisy/data_display/badge_component.rb +122 -4
- data/app/components/daisy/data_display/card_component.html.haml +1 -1
- data/app/components/daisy/data_display/card_component.rb +20 -6
- data/app/components/daisy/data_display/chat_component.rb +2 -2
- data/app/components/daisy/data_display/collapse_component.rb +9 -5
- data/app/components/daisy/data_display/countdown_component.rb +15 -5
- data/app/components/daisy/data_display/figure_component.rb +8 -3
- data/app/components/daisy/data_display/kbd_component.rb +13 -4
- data/app/components/daisy/data_display/list_component.html.haml +5 -0
- data/app/components/daisy/data_display/list_component.rb +82 -0
- data/app/components/daisy/data_display/list_item_component.rb +39 -0
- data/app/components/daisy/data_display/stat_component.html.haml +5 -6
- data/app/components/daisy/data_display/stat_component.rb +21 -8
- data/app/components/daisy/data_display/status_component.rb +47 -0
- data/app/components/daisy/data_display/timeline_component.rb +1 -1
- data/app/components/daisy/data_input/cally_component.html.haml +14 -0
- data/app/components/daisy/data_input/cally_component.rb +182 -0
- data/app/components/daisy/data_input/cally_input_component.html.haml +5 -0
- data/app/components/daisy/data_input/cally_input_component.rb +165 -0
- data/app/components/daisy/data_input/cally_input_controller.js +235 -0
- data/app/components/daisy/data_input/checkbox_component.html.haml +20 -0
- data/app/components/daisy/data_input/checkbox_component.rb +106 -0
- data/app/components/daisy/data_input/fieldset_component.html.haml +8 -0
- data/app/components/daisy/data_input/fieldset_component.rb +57 -0
- data/app/components/daisy/data_input/file_input_component.rb +98 -0
- data/app/components/daisy/data_input/filter_component.html.haml +3 -0
- data/app/components/daisy/data_input/filter_component.rb +221 -0
- data/app/components/daisy/data_input/label_component.rb +84 -0
- data/app/components/daisy/data_input/radio_button_component.rb +87 -0
- data/app/components/daisy/data_input/range_component.rb +95 -0
- data/app/components/daisy/data_input/rating_component.html.haml +11 -0
- data/app/components/daisy/data_input/rating_component.rb +139 -0
- data/app/components/daisy/data_input/select_component.html.haml +27 -0
- data/app/components/daisy/data_input/select_component.rb +320 -0
- data/app/components/daisy/data_input/text_area_component.rb +127 -0
- data/app/components/daisy/data_input/text_input_component.html.haml +27 -0
- data/app/components/daisy/data_input/text_input_component.rb +142 -0
- data/app/components/daisy/data_input/toggle_component.rb +48 -0
- data/app/components/daisy/feedback/alert_component.html.haml +1 -1
- data/app/components/daisy/feedback/alert_component.rb +86 -2
- data/app/components/daisy/feedback/loading_component.rb +10 -3
- data/app/components/daisy/feedback/skeleton_component.rb +1 -1
- data/app/components/daisy/layout/divider_component.rb +4 -2
- data/app/components/daisy/layout/drawer_component.html.haml +0 -1
- data/app/components/daisy/layout/footer_component.rb +6 -6
- data/app/components/daisy/mockup/device_component.rb +15 -18
- data/app/components/daisy/navigation/breadcrumbs_component.html.haml +0 -1
- data/app/components/daisy/navigation/breadcrumbs_component.rb +84 -9
- data/app/components/daisy/navigation/dock_component.rb +146 -0
- data/app/components/daisy/navigation/link_component.rb +18 -9
- data/app/components/daisy/navigation/menu_component.rb +15 -9
- data/app/components/daisy/navigation/navbar_component.html.haml +1 -1
- data/app/components/daisy/navigation/navbar_component.rb +2 -13
- data/app/components/daisy/navigation/steps_component.rb +6 -6
- data/app/components/daisy/navigation/tabs_component.html.haml +0 -1
- data/app/components/daisy/navigation/tabs_component.rb +26 -16
- data/app/components/hero/icon_component.rb +15 -5
- data/app/helpers/daisy/form_builder_helper.rb +186 -0
- data/app/views/examples/daisy/data_input/filters.html.haml +62 -0
- data/lib/daisy.rb +5 -0
- data/lib/hero.rb +1 -1
- data/lib/loco_motion/base_component.rb +53 -3
- data/lib/loco_motion/component_config.rb +1 -0
- data/lib/loco_motion/concerns/iconable_component.rb +134 -0
- data/lib/loco_motion/concerns/labelable_component.rb +142 -0
- data/lib/loco_motion/concerns/linkable_component.rb +40 -0
- data/lib/loco_motion/concerns/tippable_component.rb +25 -10
- data/lib/loco_motion/engine.rb +6 -0
- data/lib/loco_motion/helpers.rb +38 -17
- data/lib/loco_motion/patches/view_component/slot_loco_parent_patch.rb +37 -0
- data/lib/loco_motion/patches/view_component/slotable_default_patch.rb +21 -0
- data/lib/loco_motion/version.rb +1 -1
- data/lib/loco_motion.rb +12 -2
- metadata +93 -21
- data/app/components/daisy/actions/theme_controller_component.html.haml +0 -5
- data/app/components/daisy/layout/artboard_component.rb +0 -59
- 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,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
|