loco_motion-rails 0.4.0 → 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 +6 -1
- 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 +21 -7
- 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 +6 -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 +2 -2
- data/app/components/daisy/data_input/radio_button_component.rb +1 -1
- data/app/components/daisy/data_input/rating_component.html.haml +0 -2
- data/app/components/daisy/data_input/rating_component.rb +3 -2
- data/app/components/daisy/data_input/select_component.html.haml +27 -15
- data/app/components/daisy/data_input/select_component.rb +152 -10
- data/app/components/daisy/data_input/text_area_component.rb +11 -8
- data/app/components/daisy/data_input/text_input_component.html.haml +25 -4
- data/app/components/daisy/data_input/text_input_component.rb +38 -36
- data/app/components/daisy/data_input/toggle_component.rb +12 -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 +30 -3
- data/app/views/examples/daisy/data_input/filters.html.haml +62 -0
- data/lib/hero.rb +1 -1
- data/lib/loco_motion/base_component.rb +44 -1
- 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/helpers.rb +27 -18
- 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 +65 -19
- 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
@@ -8,21 +8,21 @@
|
|
8
8
|
# @slot tabs+ {Daisy::Navigation::TabsComponent::TabComponent} The individual
|
9
9
|
# tabs to display.
|
10
10
|
#
|
11
|
-
# @
|
12
|
-
# = daisy_tabs(css: "tabs-
|
11
|
+
# @loco_example Basic tabs with links
|
12
|
+
# = daisy_tabs(css: "tabs-border") do |tabs|
|
13
13
|
# - tabs.with_tab(title: "Home", active: true)
|
14
14
|
# - tabs.with_tab(title: "Click Me", html: { onclick: "alert('Clicked!')" })
|
15
15
|
# - tabs.with_tab(title: "Google", href: "https://google.com", target: "_blank")
|
16
16
|
#
|
17
|
-
# @
|
18
|
-
# = daisy_tabs(css: "tabs-
|
17
|
+
# @loco_example Radio button tabs with content
|
18
|
+
# = daisy_tabs(css: "tabs-lift", radio: true) do |tabs|
|
19
19
|
# - tabs.with_tab(title: "Tab 1", checked: true) do
|
20
20
|
# %p Tab 1 content
|
21
21
|
# - tabs.with_tab(title: "Tab 2") do
|
22
22
|
# %p Tab 2 content
|
23
23
|
#
|
24
|
-
# @
|
25
|
-
# = daisy_tabs(css: "tabs-
|
24
|
+
# @loco_example Tabs with custom titles and content
|
25
|
+
# = daisy_tabs(css: "tabs-lift") do |tabs|
|
26
26
|
# - tabs.with_tab do |tab|
|
27
27
|
# - tab.with_title do
|
28
28
|
# .flex.gap-2
|
@@ -31,6 +31,16 @@
|
|
31
31
|
# - tab.with_custom_content(css: "tab-content p-4") do
|
32
32
|
# %p Welcome home!
|
33
33
|
#
|
34
|
+
# @loco_example Tabs with different sizes
|
35
|
+
# = daisy_tabs(css: "tabs-border tabs-xl") do |tabs|
|
36
|
+
# - tabs.with_tab(title: "Extra Large Tab", active: true)
|
37
|
+
# - tabs.with_tab(title: "Another Tab")
|
38
|
+
#
|
39
|
+
# @loco_example Tabs with different sizes
|
40
|
+
# = daisy_tabs(css: "tabs-border tabs-lg") do |tabs|
|
41
|
+
# - tabs.with_tab(title: "Large Tab", active: true)
|
42
|
+
# - tabs.with_tab(title: "Another Tab")
|
43
|
+
#
|
34
44
|
class Daisy::Navigation::TabsComponent < LocoMotion::BaseComponent
|
35
45
|
|
36
46
|
#
|
@@ -45,21 +55,21 @@ class Daisy::Navigation::TabsComponent < LocoMotion::BaseComponent
|
|
45
55
|
# @slot custom_content Custom content to be rendered after the tab. Use this
|
46
56
|
# instead of the block content for complete control over the content's HTML.
|
47
57
|
#
|
48
|
-
# @
|
58
|
+
# @loco_example Basic tab with title
|
49
59
|
# = tabs.with_tab(title: "Home")
|
50
60
|
#
|
51
|
-
# @
|
61
|
+
# @loco_example Tab with custom title
|
52
62
|
# = tabs.with_tab do |tab|
|
53
63
|
# - tab.with_title do
|
54
64
|
# .flex.gap-2
|
55
65
|
# = hero_icon("home")
|
56
66
|
# Home
|
57
67
|
#
|
58
|
-
# @
|
68
|
+
# @loco_example Tab with content
|
59
69
|
# = tabs.with_tab(title: "Content") do
|
60
70
|
# %p This is the tab's content
|
61
71
|
#
|
62
|
-
# @
|
72
|
+
# @loco_example Tab with custom content
|
63
73
|
# = tabs.with_tab do |tab|
|
64
74
|
# - tab.with_custom_content(css: "tab-content p-4") do
|
65
75
|
# %p Custom content with custom wrapper
|
@@ -94,7 +104,7 @@ class Daisy::Navigation::TabsComponent < LocoMotion::BaseComponent
|
|
94
104
|
#
|
95
105
|
# @option kws css [String] Additional CSS classes for styling. Common
|
96
106
|
# options include:
|
97
|
-
# - Size: `tab-lg`, `tab-md
|
107
|
+
# - Size: `tab-lg`, `tab-md` (default), `tab-sm`, `tab-xs`
|
98
108
|
# - Width: `w-full`, `!w-14`
|
99
109
|
# - Cursor: `cursor-pointer`, `!cursor-auto`
|
100
110
|
#
|
@@ -112,9 +122,9 @@ class Daisy::Navigation::TabsComponent < LocoMotion::BaseComponent
|
|
112
122
|
|
113
123
|
def before_render
|
114
124
|
# Reset the name to the config option or the parent name if available
|
115
|
-
@name = config_option(:name, loco_parent
|
125
|
+
@name = config_option(:name, loco_parent.name)
|
116
126
|
|
117
|
-
if loco_parent
|
127
|
+
if loco_parent.radio?
|
118
128
|
setup_radio_button
|
119
129
|
else
|
120
130
|
setup_component
|
@@ -157,7 +167,7 @@ class Daisy::Navigation::TabsComponent < LocoMotion::BaseComponent
|
|
157
167
|
# custom_content.to_s if custom_content?
|
158
168
|
|
159
169
|
capture do
|
160
|
-
if loco_parent
|
170
|
+
if loco_parent.radio?
|
161
171
|
concat(part(:component))
|
162
172
|
else
|
163
173
|
concat(part(:component) { concat(title? ? title : @simple_title) })
|
@@ -187,8 +197,8 @@ class Daisy::Navigation::TabsComponent < LocoMotion::BaseComponent
|
|
187
197
|
#
|
188
198
|
# @option kws css [String] Additional CSS classes for styling. Common
|
189
199
|
# options include:
|
190
|
-
# - Style: `tabs-
|
191
|
-
# - Size: `tabs-lg`, `tabs-md
|
200
|
+
# - Style: `tabs-border`, `tabs-lift`
|
201
|
+
# - Size: `tabs-xl`, `tabs-lg`, `tabs-md` (default), `tabs-sm`, `tabs-xs`
|
192
202
|
# - Width: `w-full`, `w-[500px]`
|
193
203
|
#
|
194
204
|
def initialize(*args, **kws, &block)
|
@@ -8,19 +8,22 @@
|
|
8
8
|
# `:where()` pseudo-class to ensure our default classes have the lowest CSS
|
9
9
|
# specificity.
|
10
10
|
#
|
11
|
-
# @
|
11
|
+
# @loco_example Basic icon usage
|
12
12
|
# = hero_icon("academic-cap")
|
13
13
|
# = hero_icon(icon: "adjustments-horizontal")
|
14
14
|
# %span.text-blue-500
|
15
15
|
# = hero_icon("archive-box")
|
16
16
|
#
|
17
|
-
# @
|
17
|
+
# @loco_example Customized icons
|
18
18
|
# = hero_icon("no-symbol", css: "size-4 text-red-600")
|
19
19
|
# = hero_icon("arrow-trending-up", css: "size-10 text-green-600")
|
20
20
|
# = hero_icon("exclamation-triangle", css: "size-14 text-yellow-400 animate-pulse")
|
21
21
|
#
|
22
22
|
class Hero::IconComponent < LocoMotion::BaseComponent
|
23
|
-
|
23
|
+
# Tippable concern provides tooltip functionality.
|
24
|
+
include LocoMotion::Concerns::TippableComponent
|
25
|
+
|
26
|
+
set_component_name :icon
|
24
27
|
|
25
28
|
# Create a new instance of the IconComponent.
|
26
29
|
#
|
@@ -41,17 +44,24 @@ class Hero::IconComponent < LocoMotion::BaseComponent
|
|
41
44
|
# - Color: `text-red-600`, `text-green-600`, `text-yellow-400`
|
42
45
|
# - Animation: `animate-pulse`, `animate-spin`
|
43
46
|
#
|
47
|
+
# @option kws tip [String] The tooltip text to display when hovering over
|
48
|
+
# the icon.
|
49
|
+
#
|
44
50
|
def initialize(*args, **kws, &block)
|
45
51
|
super
|
46
52
|
|
47
53
|
# Accept either the :icon keyword argument or the first positional argument
|
48
54
|
@icon = config_option(:icon, args[0])
|
49
55
|
@variant = config_option(:variant)
|
56
|
+
|
57
|
+
@css = config_option(:css, "")
|
50
58
|
end
|
51
59
|
|
52
60
|
def before_render
|
61
|
+
super
|
62
|
+
|
53
63
|
add_html(:component, { variant: @variant }) if @variant
|
54
|
-
add_css(:component, "
|
64
|
+
add_css(:component, "where:size-5") unless @css.include?("size-")
|
55
65
|
end
|
56
66
|
|
57
67
|
#
|
@@ -62,6 +72,6 @@ class Hero::IconComponent < LocoMotion::BaseComponent
|
|
62
72
|
# additional whitespace gets added to the output.
|
63
73
|
#
|
64
74
|
def call
|
65
|
-
|
75
|
+
heroicon(@icon, **rendered_html(:component))
|
66
76
|
end
|
67
77
|
end
|
@@ -106,9 +106,14 @@ module Daisy
|
|
106
106
|
render_daisy_component(Daisy::DataInput::TextAreaComponent, method, **options)
|
107
107
|
end
|
108
108
|
|
109
|
+
# Add the daisy_cally_input method to FormBuilder
|
110
|
+
def daisy_cally_input(method, **options, &block)
|
111
|
+
render_daisy_component(Daisy::DataInput::CallyInputComponent, method, **options, &block)
|
112
|
+
end
|
113
|
+
|
109
114
|
# Add the daisy_select method to FormBuilder
|
110
115
|
def daisy_select(method, options: nil, option_groups: nil, placeholder: nil,
|
111
|
-
|
116
|
+
options_css: nil, options_html: {}, **args, &block)
|
112
117
|
# Extract the name from the form builder's object_name and method
|
113
118
|
name = "#{object_name}[#{method}]"
|
114
119
|
|
@@ -132,9 +137,31 @@ module Daisy
|
|
132
137
|
)
|
133
138
|
end
|
134
139
|
|
140
|
+
# Add the daisy_filter method to FormBuilder
|
141
|
+
def daisy_filter(method, options: nil, **args, &block)
|
142
|
+
# Extract the name from the form builder's object_name and method
|
143
|
+
name = "#{object_name}[#{method}]"
|
144
|
+
|
145
|
+
# Get the current value from the object
|
146
|
+
value = object.try(method)
|
147
|
+
|
148
|
+
# Generate a default ID if not provided
|
149
|
+
id = args[:id] || "#{object_name}_#{method}"
|
150
|
+
|
151
|
+
# Build the component with the extracted form values and any additional options
|
152
|
+
@template.daisy_filter(
|
153
|
+
name: name,
|
154
|
+
id: id,
|
155
|
+
value: value,
|
156
|
+
options: options,
|
157
|
+
**args,
|
158
|
+
&block
|
159
|
+
)
|
160
|
+
end
|
161
|
+
|
135
162
|
private
|
136
163
|
|
137
|
-
def render_daisy_component(component_class, method, **options)
|
164
|
+
def render_daisy_component(component_class, method, **options, &block)
|
138
165
|
# Get the object name from the form builder
|
139
166
|
object_name = @object_name.to_s
|
140
167
|
|
@@ -148,7 +175,7 @@ module Daisy
|
|
148
175
|
options[:value] ||= object.try(method)
|
149
176
|
|
150
177
|
# Render the component
|
151
|
-
@template.render
|
178
|
+
@template.render(component_class.new(**options), &block)
|
152
179
|
end
|
153
180
|
end
|
154
181
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
= doc_title(title: "Filters", comp: @comp) do |title|
|
2
|
+
:markdown
|
3
|
+
The Filter component is a group of radio buttons where choosing one option
|
4
|
+
hides the others and shows a reset button.
|
5
|
+
|
6
|
+
= doc_example(title: "Basic Usage") do |doc|
|
7
|
+
- doc.with_description do
|
8
|
+
:markdown
|
9
|
+
The basic filter component takes an `options` array and uses a div element
|
10
|
+
with radio buttons styled as buttons.
|
11
|
+
|
12
|
+
= daisy_filter(name: "frameworks", options: ["Svelte", "Vue", "React"])
|
13
|
+
|
14
|
+
|
15
|
+
= doc_example(title: "With Hash Options") do |doc|
|
16
|
+
- doc.with_description do
|
17
|
+
:markdown
|
18
|
+
You can also provide options as a hash with values and labels to customize
|
19
|
+
the display.
|
20
|
+
|
21
|
+
:ruby
|
22
|
+
options = [
|
23
|
+
{ label: "Ruby", value: "ruby" },
|
24
|
+
{ label: "JavaScript", value: "js" },
|
25
|
+
{ label: "Python", value: "py" }
|
26
|
+
]
|
27
|
+
|
28
|
+
= daisy_filter(name: "languages", options: options)
|
29
|
+
|
30
|
+
|
31
|
+
= doc_example(title: "Custom Button Styles") do |doc|
|
32
|
+
- doc.with_description do
|
33
|
+
:markdown
|
34
|
+
You can customize the styles of the buttons and reset button using CSS classes.
|
35
|
+
|
36
|
+
= daisy_filter(name: "priorities", css: "items-center") do |f|
|
37
|
+
- f.with_reset_button(css: "btn-accent btn-sm rounded-full")
|
38
|
+
- f.with_option(label: "Low", css: "btn-outline btn-success")
|
39
|
+
- f.with_option(label: "Medium", css: "btn-outline btn-warning")
|
40
|
+
- f.with_option(label: "High", css: "btn-outline btn-error")
|
41
|
+
|
42
|
+
|
43
|
+
= doc_example(title: "Filter Within Form") do |doc|
|
44
|
+
- doc.with_description do
|
45
|
+
:markdown
|
46
|
+
Filters can be used within forms to submit the selected value.
|
47
|
+
|
48
|
+
= form_with(url: "", method: :get, scope: :search, class: "mb-4") do |form|
|
49
|
+
= daisy_filter(name: "categories") do |f|
|
50
|
+
- f.with_option(label: "Category 1")
|
51
|
+
- f.with_option(label: "Category 2")
|
52
|
+
= form.submit "Apply Filters", class: "btn btn-primary mt-4"
|
53
|
+
|
54
|
+
|
55
|
+
= doc_example(title: "Form Builder") do |doc|
|
56
|
+
- doc.with_description do
|
57
|
+
:markdown
|
58
|
+
You can also use the form builder to create a filter.
|
59
|
+
|
60
|
+
= form_with(url: "", method: :get, scope: :search, class: "mb-4") do |form|
|
61
|
+
= form.daisy_filter(:category, options: ["Technology", "Science", "Arts"])
|
62
|
+
= form.submit "Apply Filters", class: "btn btn-primary mt-4"
|
data/lib/hero.rb
CHANGED
@@ -3,13 +3,17 @@ class LocoMotion::BaseComponent < ViewComponent::Base
|
|
3
3
|
SELF_CLOSING_TAGS = %i[area base br col embed hr img input keygen link meta param source track wbr].freeze
|
4
4
|
EMPTY_PART_IGNORED_TAGS = %i[textarea].freeze
|
5
5
|
|
6
|
-
include
|
6
|
+
include RailsHeroicon::Helper
|
7
7
|
|
8
8
|
class_attribute :component_name
|
9
9
|
class_attribute :component_parts, default: { component: {} }
|
10
10
|
class_attribute :valid_modifiers, default: []
|
11
11
|
class_attribute :valid_sizes, default: []
|
12
12
|
|
13
|
+
# Hooks for concerns to register initialization and setup methods
|
14
|
+
class_attribute :component_initializers, default: []
|
15
|
+
class_attribute :component_setups, default: []
|
16
|
+
|
13
17
|
#
|
14
18
|
# Return the current configuration of this component.
|
15
19
|
#
|
@@ -31,6 +35,24 @@ class LocoMotion::BaseComponent < ViewComponent::Base
|
|
31
35
|
|
32
36
|
# Create our config object
|
33
37
|
@config = LocoMotion::ComponentConfig.new(self, **kws, &block)
|
38
|
+
|
39
|
+
# Run registered initializer hooks from concerns
|
40
|
+
self.class.component_initializers.each { |initializer| send(initializer) }
|
41
|
+
|
42
|
+
# Allow certain components to skip styling if they are being inherited
|
43
|
+
@skip_styling = config_option(:skip_styling, false)
|
44
|
+
|
45
|
+
# Allow manual passing of the loco parent on init if it's not auto-set
|
46
|
+
# via slots
|
47
|
+
@loco_parent = kws[:loco_parent] if kws.key?(:loco_parent)
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Run registered setup hooks from concerns before rendering.
|
52
|
+
#
|
53
|
+
def before_render
|
54
|
+
# Note: ViewComponent::Base does not define before_render, so no super call needed.
|
55
|
+
self.class.component_setups.each { |setup| send(setup) }
|
34
56
|
end
|
35
57
|
|
36
58
|
#
|
@@ -87,6 +109,26 @@ class LocoMotion::BaseComponent < ViewComponent::Base
|
|
87
109
|
end
|
88
110
|
end
|
89
111
|
|
112
|
+
#
|
113
|
+
# Register an instance method to be called during component initialization.
|
114
|
+
#
|
115
|
+
# @param method_name [Symbol] The name of the instance method to call.
|
116
|
+
#
|
117
|
+
def self.register_component_initializer(method_name)
|
118
|
+
# Ensure we don't modify the parent class's array directly
|
119
|
+
self.component_initializers += [method_name.to_sym]
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Register an instance method to be called before component rendering.
|
124
|
+
#
|
125
|
+
# @param method_name [Symbol] The name of the instance method to call.
|
126
|
+
#
|
127
|
+
def self.register_component_setup(method_name)
|
128
|
+
# Ensure we don't modify the parent class's array directly
|
129
|
+
self.component_setups += [method_name.to_sym]
|
130
|
+
end
|
131
|
+
|
90
132
|
#
|
91
133
|
# Defines a single modifier of this component. Modifiers control certain
|
92
134
|
# rendering aspects of the component.
|
@@ -373,6 +415,7 @@ class LocoMotion::BaseComponent < ViewComponent::Base
|
|
373
415
|
"@valid_sizes=#{valid_sizes.inspect}",
|
374
416
|
"@config=#{@config.inspect}",
|
375
417
|
"@component_parts=#{parts.inspect}",
|
418
|
+
"@loco_parent=#{loco_parent.inspect}",
|
376
419
|
].join(" ") + ">"
|
377
420
|
end
|
378
421
|
end
|
@@ -40,6 +40,7 @@ class LocoMotion::ComponentConfig
|
|
40
40
|
@parts[:component][:user_tag_name] = kws[:tag_name] if kws[:tag_name]
|
41
41
|
@parts[:component][:user_css].push(kws[:css]) if kws[:css]
|
42
42
|
@parts[:component][:user_html].deep_merge!(kws[:html]) if kws[:html]
|
43
|
+
@parts[:component][:user_stimulus_controllers].push(kws[:controller]) if kws[:controller]
|
43
44
|
@parts[:component][:user_stimulus_controllers].push(kws[:controllers]) if kws[:controllers]
|
44
45
|
end
|
45
46
|
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module LocoMotion
|
4
|
+
module Concerns
|
5
|
+
#
|
6
|
+
# The IconableComponent concern provides functionality for components that
|
7
|
+
# display icons. It supports both left and right icons and allows for
|
8
|
+
# customization of their CSS classes and HTML attributes.
|
9
|
+
#
|
10
|
+
module IconableComponent
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
included do |base|
|
14
|
+
base.register_component_initializer(:_initialize_iconable_component)
|
15
|
+
base.register_component_setup(:_setup_iconable_component)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
#
|
21
|
+
# Initialize icon-related options.
|
22
|
+
#
|
23
|
+
# @option kws icon [String] The name of Hero icon to render. This is an
|
24
|
+
# alias of `left_icon`.
|
25
|
+
#
|
26
|
+
# @option kws icon_css [String] The CSS classes to apply to the icon. This
|
27
|
+
# is an alias of `left_icon_css`.
|
28
|
+
#
|
29
|
+
# @option kws icon_html [Hash] Additional HTML attributes to apply to the
|
30
|
+
# icon. This is an alias of `left_icon_html`.
|
31
|
+
#
|
32
|
+
# @option kws left_icon [String] The name of Hero icon to render to the
|
33
|
+
# left of the content.
|
34
|
+
#
|
35
|
+
# @option kws left_icon_css [String] The CSS classes to apply to the left
|
36
|
+
# icon.
|
37
|
+
#
|
38
|
+
# @option kws left_icon_html [Hash] Additional HTML attributes to apply to
|
39
|
+
# the left icon.
|
40
|
+
#
|
41
|
+
# @option kws right_icon [String] The name of Hero icon to render to the
|
42
|
+
# right of the content.
|
43
|
+
#
|
44
|
+
# @option kws right_icon_css [String] The CSS classes to apply to the right
|
45
|
+
# icon.
|
46
|
+
#
|
47
|
+
# @option kws right_icon_html [Hash] Additional HTML attributes to apply to
|
48
|
+
# the right icon.
|
49
|
+
#
|
50
|
+
def _initialize_iconable_component
|
51
|
+
@icon = config_option(:icon)
|
52
|
+
@icon_css = config_option(:icon_css, default_icon_size)
|
53
|
+
@icon_options = config_option(:icon_options, {})
|
54
|
+
@icon_html = config_option(:icon_html, {})
|
55
|
+
|
56
|
+
@left_icon = config_option(:left_icon, @icon)
|
57
|
+
@left_icon_css = config_option(:left_icon_css, @icon_css)
|
58
|
+
@left_icon_options = config_option(:left_icon_options, @icon_html)
|
59
|
+
@left_icon_html = config_option(:left_icon_html, @icon_html)
|
60
|
+
|
61
|
+
@right_icon = config_option(:right_icon)
|
62
|
+
@right_icon_css = config_option(:right_icon_css, @icon_css)
|
63
|
+
@right_icon_options = config_option(:right_icon_options, {})
|
64
|
+
@right_icon_html = config_option(:right_icon_html, @icon_html)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Configure CSS classes for a component with icons.
|
69
|
+
# This adds necessary classes for proper icon spacing and alignment.
|
70
|
+
#
|
71
|
+
def _setup_iconable_component
|
72
|
+
if @icon || @left_icon || @right_icon
|
73
|
+
add_css(:component, "where:inline-flex where:items-center where:gap-2")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def default_icon_size
|
78
|
+
"where:size-5"
|
79
|
+
end
|
80
|
+
|
81
|
+
public # Ensure these helper methods remain public
|
82
|
+
|
83
|
+
#
|
84
|
+
# Returns the HTML attributes for the left icon.
|
85
|
+
#
|
86
|
+
# @return [Hash] HTML attributes for the left icon
|
87
|
+
#
|
88
|
+
def left_icon_html
|
89
|
+
{ class: @left_icon_css }.merge(@left_icon_html)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Returns the HTML attributes for the right icon.
|
94
|
+
#
|
95
|
+
# @return [Hash] HTML attributes for the right icon
|
96
|
+
#
|
97
|
+
def right_icon_html
|
98
|
+
{ class: @right_icon_css }.merge(@right_icon_html)
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Determines if any icons are present in the component.
|
103
|
+
#
|
104
|
+
# @return [Boolean] true if any icons are configured, false otherwise
|
105
|
+
#
|
106
|
+
def has_icons?
|
107
|
+
@left_icon.present? || @right_icon.present?
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Renders the left icon as a Hero::IconComponent instance.
|
112
|
+
#
|
113
|
+
# @return [String] The rendered HTML for the icon
|
114
|
+
#
|
115
|
+
def render_left_icon
|
116
|
+
return unless @left_icon.present?
|
117
|
+
|
118
|
+
hero_icon(@left_icon, css: @left_icon_css, html: @left_icon_html, **@left_icon_options)
|
119
|
+
end
|
120
|
+
alias_method :render_icon, :render_left_icon
|
121
|
+
|
122
|
+
#
|
123
|
+
# Renders the right icon using a hero icon.
|
124
|
+
#
|
125
|
+
# @return [String] The rendered HTML for the icon
|
126
|
+
#
|
127
|
+
def render_right_icon
|
128
|
+
return unless @right_icon.present?
|
129
|
+
|
130
|
+
hero_icon(@right_icon, css: @right_icon_css, html: @right_icon_html, **@right_icon_options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LocoMotion
|
4
|
+
module Concerns
|
5
|
+
#
|
6
|
+
# Can be included in relevant components to add labeling functionality.
|
7
|
+
# This adds support for start, end, and floating labels that can either be
|
8
|
+
# provided as plain text or customized via slots.
|
9
|
+
#
|
10
|
+
# @loco_example Basic usage with a start label
|
11
|
+
# class MyInputComponent < LocoMotion::BaseComponent
|
12
|
+
# include LocoMotion::Concerns::LabelableComponent
|
13
|
+
# # component implementation ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# = daisy_my_input(name: "username", start: "Username")
|
17
|
+
#
|
18
|
+
# @loco_example With an end label (useful for checkboxes/radios)
|
19
|
+
# = daisy_checkbox(name: "terms", end: "I agree to the terms")
|
20
|
+
#
|
21
|
+
# @loco_example With a floating label
|
22
|
+
# = daisy_text_input(name: "email", floating: "Email Address")
|
23
|
+
#
|
24
|
+
# @loco_example Using a custom slot for the label
|
25
|
+
# = daisy_text_input(name: "password") do |input|
|
26
|
+
# - input.with_floating do
|
27
|
+
# Password
|
28
|
+
# %span.text-red-500 *
|
29
|
+
#
|
30
|
+
module LabelableComponent
|
31
|
+
extend ActiveSupport::Concern
|
32
|
+
|
33
|
+
#
|
34
|
+
# Called when the module is included in a component class.
|
35
|
+
# Sets up the necessary parts & slots for custom label content.
|
36
|
+
#
|
37
|
+
included do
|
38
|
+
define_parts :label_wrapper, :start, :end, :floating
|
39
|
+
|
40
|
+
renders_one :start
|
41
|
+
renders_one :end
|
42
|
+
renders_one :floating
|
43
|
+
|
44
|
+
# NOTE: We DO NOT define attr_reader properties here because it can
|
45
|
+
# cause confusion / problems with the parts and slots.
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Initializes the component and sets up the label options.
|
50
|
+
#
|
51
|
+
# @param instance_args [Array] Positional arguments passed to the component
|
52
|
+
#
|
53
|
+
# @param instance_kws [Hash] Keyword arguments passed to the component
|
54
|
+
#
|
55
|
+
# @option instance_kws [String, nil] :start Text to display in the start
|
56
|
+
# label position
|
57
|
+
#
|
58
|
+
# @option instance_kws [String, nil] :end Text to display in the end
|
59
|
+
# label position
|
60
|
+
#
|
61
|
+
# @option instance_kws [String, nil] :floating Text to display in the
|
62
|
+
# floating label position
|
63
|
+
#
|
64
|
+
# @option instance_kws [String, nil] :placeholder The input's placeholder
|
65
|
+
# text. If not provided and `floating_placeholder` is set, it will use
|
66
|
+
# that value.
|
67
|
+
#
|
68
|
+
# @option instance_kws [String, nil] :floating_placeholder Text to use for
|
69
|
+
# both the floating label and the input placeholder. This is a
|
70
|
+
# convenience option that sets both the `floating` and `placeholder`
|
71
|
+
# options to the same value. Both `floating` and `placeholder` take
|
72
|
+
# precedence over `floating_placeholder`.
|
73
|
+
#
|
74
|
+
# @param instance_block [Proc] Block passed to the component for rendering
|
75
|
+
# custom content
|
76
|
+
#
|
77
|
+
def initialize(*instance_args, **instance_kws, &instance_block)
|
78
|
+
super(*instance_args, **instance_kws, &instance_block)
|
79
|
+
|
80
|
+
@floating_placeholder = config_option(:floating_placeholder)
|
81
|
+
|
82
|
+
@start = config_option(:start)
|
83
|
+
@end = config_option(:end)
|
84
|
+
@floating = config_option(:floating, @floating_placeholder)
|
85
|
+
@placeholder = config_option(:placeholder, @floating_placeholder)
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Sets up the tag names for the label parts before rendering the component.
|
90
|
+
# This method is called automatically during the component rendering
|
91
|
+
# lifecycle.
|
92
|
+
#
|
93
|
+
# Note that CSS classes for labels must be handled by the implementing
|
94
|
+
# component since requirements differ for each type of input component.
|
95
|
+
#
|
96
|
+
def before_render
|
97
|
+
super
|
98
|
+
|
99
|
+
set_tag_name(:label_wrapper, :label)
|
100
|
+
set_tag_name(:start, :span)
|
101
|
+
set_tag_name(:end, :span)
|
102
|
+
set_tag_name(:floating, :span)
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Checks if any type of label is present.
|
107
|
+
#
|
108
|
+
# @return [Boolean] true if any label is present, false otherwise
|
109
|
+
#
|
110
|
+
def has_any_label?
|
111
|
+
has_start_label? || has_end_label? || has_floating_label?
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# Checks if a start label is present.
|
116
|
+
#
|
117
|
+
# @return [Boolean] true if start label is present, false otherwise
|
118
|
+
#
|
119
|
+
def has_start_label?
|
120
|
+
start? || @start || config_option(:start).present?
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Checks if an end label is present.
|
125
|
+
#
|
126
|
+
# @return [Boolean] true if end label is present, false otherwise
|
127
|
+
#
|
128
|
+
def has_end_label?
|
129
|
+
end? || @end || config_option(:end).present?
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Checks if a floating label is present.
|
134
|
+
#
|
135
|
+
# @return [Boolean] true if floating label is present, false otherwise
|
136
|
+
#
|
137
|
+
def has_floating_label?
|
138
|
+
floating? || @floating || config_option(:floating).present?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|