maquina-components 0.2.0 → 0.3.1
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 +77 -0
- data/app/assets/stylesheets/calendar.css +222 -0
- data/app/assets/stylesheets/combobox.css +218 -0
- data/app/assets/stylesheets/date_picker.css +172 -0
- data/app/assets/stylesheets/toast.css +433 -0
- data/app/assets/tailwind/maquina_components_engine/engine.css +16 -14
- data/app/helpers/maquina_components/calendar_helper.rb +196 -0
- data/app/helpers/maquina_components/combobox_helper.rb +300 -0
- data/app/helpers/maquina_components/icons_helper.rb +220 -0
- data/app/helpers/maquina_components/table_helper.rb +9 -10
- data/app/helpers/maquina_components/toast_helper.rb +115 -0
- data/app/javascript/controllers/calendar_controller.js +394 -0
- data/app/javascript/controllers/combobox_controller.js +325 -0
- data/app/javascript/controllers/date_picker_controller.js +261 -0
- data/app/javascript/controllers/toast_controller.js +115 -0
- data/app/javascript/controllers/toaster_controller.js +226 -0
- data/app/views/components/_calendar.html.erb +121 -0
- data/app/views/components/_combobox.html.erb +13 -0
- data/app/views/components/_date_picker.html.erb +102 -0
- data/app/views/components/_toast.html.erb +53 -0
- data/app/views/components/_toaster.html.erb +17 -0
- data/app/views/components/calendar/_header.html.erb +22 -0
- data/app/views/components/calendar/_week.html.erb +53 -0
- data/app/views/components/combobox/_content.html.erb +17 -0
- data/app/views/components/combobox/_empty.html.erb +9 -0
- data/app/views/components/combobox/_group.html.erb +8 -0
- data/app/views/components/combobox/_input.html.erb +18 -0
- data/app/views/components/combobox/_label.html.erb +8 -0
- data/app/views/components/combobox/_list.html.erb +8 -0
- data/app/views/components/combobox/_option.html.erb +24 -0
- data/app/views/components/combobox/_separator.html.erb +6 -0
- data/app/views/components/combobox/_trigger.html.erb +22 -0
- data/app/views/components/toast/_action.html.erb +14 -0
- data/app/views/components/toast/_description.html.erb +8 -0
- data/app/views/components/toast/_title.html.erb +8 -0
- data/lib/maquina_components/version.rb +1 -1
- metadata +33 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaquinaComponents
|
|
4
|
+
# Calendar Helper
|
|
5
|
+
#
|
|
6
|
+
# Provides utility methods for working with calendar and date picker data.
|
|
7
|
+
#
|
|
8
|
+
# @example Generate month data
|
|
9
|
+
# calendar_month_data(Date.current, :sunday)
|
|
10
|
+
#
|
|
11
|
+
# @example Check if date is in range
|
|
12
|
+
# calendar_date_in_range?(date, start_date, end_date)
|
|
13
|
+
#
|
|
14
|
+
module CalendarHelper
|
|
15
|
+
# Generate calendar month data
|
|
16
|
+
#
|
|
17
|
+
# @param date [Date] Any date within the target month
|
|
18
|
+
# @param week_starts_on [Symbol] :sunday or :monday
|
|
19
|
+
# @return [Hash] Month metadata and weeks array
|
|
20
|
+
def calendar_month_data(date, week_starts_on = :sunday)
|
|
21
|
+
first_of_month = date.beginning_of_month
|
|
22
|
+
last_of_month = date.end_of_month
|
|
23
|
+
|
|
24
|
+
# Calculate start of calendar grid
|
|
25
|
+
week_start = (week_starts_on == :monday) ? 1 : 0
|
|
26
|
+
days_before = (first_of_month.wday - week_start) % 7
|
|
27
|
+
calendar_start = first_of_month - days_before.days
|
|
28
|
+
|
|
29
|
+
# Calculate end of calendar grid (6 weeks max)
|
|
30
|
+
total_days = days_before + last_of_month.day
|
|
31
|
+
weeks_needed = (total_days / 7.0).ceil
|
|
32
|
+
weeks_needed = [weeks_needed, 6].min
|
|
33
|
+
calendar_end = calendar_start + (weeks_needed * 7 - 1).days
|
|
34
|
+
|
|
35
|
+
# Build weeks array
|
|
36
|
+
weeks = (calendar_start..calendar_end).each_slice(7).to_a
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
month: date.month,
|
|
40
|
+
year: date.year,
|
|
41
|
+
first_of_month: first_of_month,
|
|
42
|
+
last_of_month: last_of_month,
|
|
43
|
+
weeks: weeks,
|
|
44
|
+
week_starts_on: week_starts_on
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Format month name with year
|
|
49
|
+
#
|
|
50
|
+
# @param date [Date] Any date within the target month
|
|
51
|
+
# @param format [Symbol] :long (%B %Y) or :short (%b %Y)
|
|
52
|
+
# @return [String] Formatted month name
|
|
53
|
+
def calendar_month_name(date, format = :long)
|
|
54
|
+
case format
|
|
55
|
+
when :short
|
|
56
|
+
I18n.l(date, format: "%b %Y")
|
|
57
|
+
else
|
|
58
|
+
I18n.l(date, format: "%B %Y")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Check if a date falls within a range
|
|
63
|
+
#
|
|
64
|
+
# @param date [Date] The date to check
|
|
65
|
+
# @param start_date [Date, nil] Range start (inclusive)
|
|
66
|
+
# @param end_date [Date, nil] Range end (inclusive)
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def calendar_date_in_range?(date, start_date, end_date)
|
|
69
|
+
return false unless start_date && end_date
|
|
70
|
+
date.between?(start_date, end_date)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Generate data attributes hash for calendar
|
|
74
|
+
#
|
|
75
|
+
# @param mode [Symbol] :single or :range
|
|
76
|
+
# @param selected [Date, String, nil] Selected date
|
|
77
|
+
# @param selected_end [Date, String, nil] End date for range
|
|
78
|
+
# @param month [Integer, nil] Display month
|
|
79
|
+
# @param year [Integer, nil] Display year
|
|
80
|
+
# @return [Hash] Data attributes for use with content_tag
|
|
81
|
+
def calendar_data_attrs(mode: :single, selected: nil, selected_end: nil, month: nil, year: nil)
|
|
82
|
+
selected_str = case selected
|
|
83
|
+
when Date, Time, DateTime then selected.to_date.iso8601
|
|
84
|
+
when String then selected
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
selected_end_str = case selected_end
|
|
88
|
+
when Date, Time, DateTime then selected_end.to_date.iso8601
|
|
89
|
+
when String then selected_end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
display_date = selected_str ? Date.parse(selected_str) : Date.current
|
|
93
|
+
display_month = month || display_date.month
|
|
94
|
+
display_year = year || display_date.year
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
data: {
|
|
98
|
+
controller: "calendar",
|
|
99
|
+
component: "calendar",
|
|
100
|
+
"calendar-mode-value": mode,
|
|
101
|
+
"calendar-month-value": display_month,
|
|
102
|
+
"calendar-year-value": display_year,
|
|
103
|
+
"calendar-selected-value": selected_str,
|
|
104
|
+
"calendar-selected-end-value": selected_end_str
|
|
105
|
+
}.compact
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get weekday names based on week start
|
|
110
|
+
#
|
|
111
|
+
# @param week_starts_on [Symbol] :sunday or :monday
|
|
112
|
+
# @param format [Symbol] :short (Mo, Tu) or :narrow (M, T) or :long (Monday)
|
|
113
|
+
# @return [Array<String>]
|
|
114
|
+
def calendar_weekday_names(week_starts_on = :sunday, format = :short)
|
|
115
|
+
names = case format
|
|
116
|
+
when :narrow
|
|
117
|
+
%w[S M T W T F S]
|
|
118
|
+
when :long
|
|
119
|
+
I18n.t("date.day_names")
|
|
120
|
+
else
|
|
121
|
+
%w[Su Mo Tu We Th Fr Sa]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
(week_starts_on == :monday) ? names.rotate(1) : names
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Generate data attributes hash for date picker
|
|
128
|
+
#
|
|
129
|
+
# @param mode [Symbol] :single or :range
|
|
130
|
+
# @param selected [Date, String, nil] Selected date
|
|
131
|
+
# @param selected_end [Date, String, nil] End date for range
|
|
132
|
+
# @return [Hash] Data attributes for use with content_tag
|
|
133
|
+
def date_picker_data_attrs(mode: :single, selected: nil, selected_end: nil)
|
|
134
|
+
selected_str = case selected
|
|
135
|
+
when Date, Time, DateTime then selected.to_date.iso8601
|
|
136
|
+
when String then selected
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
selected_end_str = case selected_end
|
|
140
|
+
when Date, Time, DateTime then selected_end.to_date.iso8601
|
|
141
|
+
when String then selected_end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
{
|
|
145
|
+
data: {
|
|
146
|
+
controller: "date-picker",
|
|
147
|
+
component: "date-picker",
|
|
148
|
+
"date-picker-mode-value": mode,
|
|
149
|
+
"date-picker-selected-value": selected_str,
|
|
150
|
+
"date-picker-selected-end-value": selected_end_str
|
|
151
|
+
}.compact
|
|
152
|
+
}
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Format date for display in date picker
|
|
156
|
+
#
|
|
157
|
+
# @param date [Date, String, nil] Date to format
|
|
158
|
+
# @param format [Symbol] :short, :long, or :full
|
|
159
|
+
# @return [String, nil]
|
|
160
|
+
def date_picker_format(date, format = :long)
|
|
161
|
+
return nil unless date
|
|
162
|
+
|
|
163
|
+
date = Date.parse(date) if date.is_a?(String)
|
|
164
|
+
|
|
165
|
+
case format
|
|
166
|
+
when :short
|
|
167
|
+
I18n.l(date, format: :short)
|
|
168
|
+
when :full
|
|
169
|
+
I18n.l(date, format: :long)
|
|
170
|
+
else
|
|
171
|
+
I18n.l(date, format: :long)
|
|
172
|
+
end
|
|
173
|
+
rescue ArgumentError
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Format date range for display
|
|
178
|
+
#
|
|
179
|
+
# @param start_date [Date, String, nil] Start date
|
|
180
|
+
# @param end_date [Date, String, nil] End date
|
|
181
|
+
# @param format [Symbol] :short or :long
|
|
182
|
+
# @return [String, nil]
|
|
183
|
+
def date_picker_format_range(start_date, end_date, format = :short)
|
|
184
|
+
start_str = date_picker_format(start_date, format)
|
|
185
|
+
end_str = date_picker_format(end_date, format)
|
|
186
|
+
|
|
187
|
+
return nil unless start_str
|
|
188
|
+
|
|
189
|
+
if end_str
|
|
190
|
+
"#{start_str} - #{end_str}"
|
|
191
|
+
else
|
|
192
|
+
"#{start_str} - ..."
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaquinaComponents
|
|
4
|
+
# Combobox Helper
|
|
5
|
+
#
|
|
6
|
+
# Provides a builder pattern for creating combobox components with a clean API.
|
|
7
|
+
#
|
|
8
|
+
# @example Basic usage
|
|
9
|
+
# <%= combobox placeholder: "Select framework..." do |cb| %>
|
|
10
|
+
# <% cb.trigger %>
|
|
11
|
+
# <% cb.content do %>
|
|
12
|
+
# <% cb.input placeholder: "Search..." %>
|
|
13
|
+
# <% cb.list do %>
|
|
14
|
+
# <% cb.option value: "nextjs" do %>Next.js<% end %>
|
|
15
|
+
# <% cb.option value: "remix" do %>Remix<% end %>
|
|
16
|
+
# <% end %>
|
|
17
|
+
# <% cb.empty %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
# <% end %>
|
|
20
|
+
#
|
|
21
|
+
# @example Simple data-driven combobox
|
|
22
|
+
# <%= combobox_simple placeholder: "Select framework...",
|
|
23
|
+
# options: [
|
|
24
|
+
# { value: "nextjs", label: "Next.js" },
|
|
25
|
+
# { value: "remix", label: "Remix" }
|
|
26
|
+
# ] %>
|
|
27
|
+
#
|
|
28
|
+
# @example With groups
|
|
29
|
+
# <%= combobox placeholder: "Select..." do |cb| %>
|
|
30
|
+
# <% cb.trigger %>
|
|
31
|
+
# <% cb.content do %>
|
|
32
|
+
# <% cb.input %>
|
|
33
|
+
# <% cb.list do %>
|
|
34
|
+
# <% cb.group do %>
|
|
35
|
+
# <% cb.label "Frontend" %>
|
|
36
|
+
# <% cb.option value: "react" do %>React<% end %>
|
|
37
|
+
# <% cb.option value: "vue" do %>Vue<% end %>
|
|
38
|
+
# <% end %>
|
|
39
|
+
# <% cb.separator %>
|
|
40
|
+
# <% cb.group do %>
|
|
41
|
+
# <% cb.label "Backend" %>
|
|
42
|
+
# <% cb.option value: "rails" do %>Rails<% end %>
|
|
43
|
+
# <% end %>
|
|
44
|
+
# <% end %>
|
|
45
|
+
# <% cb.empty %>
|
|
46
|
+
# <% end %>
|
|
47
|
+
# <% end %>
|
|
48
|
+
#
|
|
49
|
+
module ComboboxHelper
|
|
50
|
+
# Renders a combobox using the builder pattern
|
|
51
|
+
#
|
|
52
|
+
# @param id [String, nil] Explicit ID for the combobox
|
|
53
|
+
# @param name [String, nil] Form field name
|
|
54
|
+
# @param value [String, nil] Currently selected value
|
|
55
|
+
# @param placeholder [String] Placeholder text
|
|
56
|
+
# @param css_classes [String] Additional CSS classes
|
|
57
|
+
# @param html_options [Hash] Additional HTML attributes
|
|
58
|
+
# @yield [ComboboxBuilder] Builder instance for constructing the combobox
|
|
59
|
+
# @return [String] Rendered HTML
|
|
60
|
+
def combobox(id: nil, name: nil, value: nil, placeholder: "Select...", css_classes: "", **html_options, &block)
|
|
61
|
+
builder = ComboboxBuilder.new(self, placeholder: placeholder)
|
|
62
|
+
combobox_id = id || "combobox-#{SecureRandom.hex(4)}"
|
|
63
|
+
builder.combobox_id = combobox_id
|
|
64
|
+
|
|
65
|
+
capture(builder, &block)
|
|
66
|
+
|
|
67
|
+
render "components/combobox",
|
|
68
|
+
id: combobox_id,
|
|
69
|
+
name: name,
|
|
70
|
+
value: value,
|
|
71
|
+
placeholder: placeholder,
|
|
72
|
+
css_classes: css_classes,
|
|
73
|
+
**html_options do |_id|
|
|
74
|
+
builder.to_html
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Renders a simple combobox from data
|
|
79
|
+
#
|
|
80
|
+
# @param options [Array<Hash>] Array of option configurations with :value and :label keys
|
|
81
|
+
# @param placeholder [String] Placeholder text
|
|
82
|
+
# @param search_placeholder [String] Search input placeholder
|
|
83
|
+
# @param empty_text [String] Text shown when no results
|
|
84
|
+
# @param value [String, nil] Currently selected value
|
|
85
|
+
# @param name [String, nil] Form field name
|
|
86
|
+
# @param trigger_options [Hash] Options for the trigger button
|
|
87
|
+
# @param content_options [Hash] Options for the content container
|
|
88
|
+
# @return [String] Rendered HTML
|
|
89
|
+
def combobox_simple(
|
|
90
|
+
options:,
|
|
91
|
+
placeholder: "Select...",
|
|
92
|
+
search_placeholder: "Search...",
|
|
93
|
+
empty_text: "No results found.",
|
|
94
|
+
value: nil,
|
|
95
|
+
name: nil,
|
|
96
|
+
trigger_options: {},
|
|
97
|
+
content_options: {}
|
|
98
|
+
)
|
|
99
|
+
combobox(placeholder: placeholder, value: value, name: name) do |cb|
|
|
100
|
+
cb.trigger(**trigger_options)
|
|
101
|
+
|
|
102
|
+
cb.content(**content_options) do
|
|
103
|
+
cb.input(placeholder: search_placeholder)
|
|
104
|
+
cb.list do
|
|
105
|
+
options.each do |opt|
|
|
106
|
+
selected = value.present? && opt[:value].to_s == value.to_s
|
|
107
|
+
cb.option(value: opt[:value], selected: selected, disabled: opt[:disabled]) do
|
|
108
|
+
opt[:label] || opt[:value]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
cb.empty(text: empty_text)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Builder class for constructing comboboxes
|
|
118
|
+
class ComboboxBuilder
|
|
119
|
+
attr_accessor :combobox_id
|
|
120
|
+
|
|
121
|
+
def initialize(view_context, placeholder:)
|
|
122
|
+
@view = view_context
|
|
123
|
+
@placeholder = placeholder
|
|
124
|
+
@parts = []
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Defines the trigger button
|
|
128
|
+
#
|
|
129
|
+
# @param variant [Symbol] Button variant
|
|
130
|
+
# @param size [Symbol] Button size
|
|
131
|
+
# @param options [Hash] Additional options
|
|
132
|
+
def trigger(variant: :outline, size: :default, **options)
|
|
133
|
+
@parts << @view.render(
|
|
134
|
+
"components/combobox/trigger",
|
|
135
|
+
for_id: combobox_id,
|
|
136
|
+
placeholder: @placeholder,
|
|
137
|
+
variant: variant,
|
|
138
|
+
size: size,
|
|
139
|
+
**options
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Defines the content popover
|
|
144
|
+
#
|
|
145
|
+
# @param align [Symbol] Horizontal alignment
|
|
146
|
+
# @param width [Symbol] Width preset
|
|
147
|
+
# @param options [Hash] Additional options
|
|
148
|
+
# @yield Block containing combobox content
|
|
149
|
+
def content(align: :start, width: :default, **options, &block)
|
|
150
|
+
content_builder = ComboboxContentBuilder.new(@view)
|
|
151
|
+
@view.capture(content_builder, &block)
|
|
152
|
+
|
|
153
|
+
@parts << @view.render(
|
|
154
|
+
"components/combobox/content",
|
|
155
|
+
id: combobox_id,
|
|
156
|
+
align: align,
|
|
157
|
+
width: width,
|
|
158
|
+
**options
|
|
159
|
+
) { content_builder.to_html }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Shortcut to add input directly (delegates to content builder)
|
|
163
|
+
def input(placeholder: "Search...", **options)
|
|
164
|
+
@current_content_builder&.input(placeholder: placeholder, **options)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Shortcut to add list directly (delegates to content builder)
|
|
168
|
+
def list(**options, &block)
|
|
169
|
+
@current_content_builder&.list(**options, &block)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Shortcut to add empty directly (delegates to content builder)
|
|
173
|
+
def empty(text: "No results found.", **options)
|
|
174
|
+
@current_content_builder&.empty(text: text, **options)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Generates the final HTML
|
|
178
|
+
def to_html
|
|
179
|
+
@view.safe_join(@parts)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Builder for combobox content
|
|
184
|
+
class ComboboxContentBuilder
|
|
185
|
+
def initialize(view_context)
|
|
186
|
+
@view = view_context
|
|
187
|
+
@parts = []
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Adds the search input
|
|
191
|
+
#
|
|
192
|
+
# @param placeholder [String] Input placeholder
|
|
193
|
+
# @param options [Hash] Additional options
|
|
194
|
+
def input(placeholder: "Search...", **options)
|
|
195
|
+
@parts << @view.render(
|
|
196
|
+
"components/combobox/input",
|
|
197
|
+
placeholder: placeholder,
|
|
198
|
+
**options
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Adds the options list container
|
|
203
|
+
#
|
|
204
|
+
# @param options [Hash] Additional options
|
|
205
|
+
# @yield Block containing options
|
|
206
|
+
def list(**options, &block)
|
|
207
|
+
list_builder = ComboboxListBuilder.new(@view)
|
|
208
|
+
@view.capture(list_builder, &block)
|
|
209
|
+
|
|
210
|
+
@parts << @view.render(
|
|
211
|
+
"components/combobox/list",
|
|
212
|
+
**options
|
|
213
|
+
) { list_builder.to_html }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Adds the empty state
|
|
217
|
+
#
|
|
218
|
+
# @param text [String] Empty state text
|
|
219
|
+
# @param options [Hash] Additional options
|
|
220
|
+
def empty(text: "No results found.", **options)
|
|
221
|
+
@parts << @view.render(
|
|
222
|
+
"components/combobox/empty",
|
|
223
|
+
text: text,
|
|
224
|
+
**options
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Generates the final HTML
|
|
229
|
+
def to_html
|
|
230
|
+
@view.safe_join(@parts)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Builder for combobox list content
|
|
235
|
+
class ComboboxListBuilder
|
|
236
|
+
def initialize(view_context)
|
|
237
|
+
@view = view_context
|
|
238
|
+
@parts = []
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Adds an option
|
|
242
|
+
#
|
|
243
|
+
# @param value [String] Option value
|
|
244
|
+
# @param selected [Boolean] Whether selected
|
|
245
|
+
# @param disabled [Boolean] Whether disabled
|
|
246
|
+
# @param options [Hash] Additional options
|
|
247
|
+
# @yield Block for option content
|
|
248
|
+
def option(value:, selected: false, disabled: false, **options, &block)
|
|
249
|
+
content = @view.capture(&block) if block
|
|
250
|
+
@parts << @view.render(
|
|
251
|
+
"components/combobox/option",
|
|
252
|
+
value: value,
|
|
253
|
+
selected: selected,
|
|
254
|
+
disabled: disabled,
|
|
255
|
+
**options
|
|
256
|
+
) { content }
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Adds a group
|
|
260
|
+
#
|
|
261
|
+
# @param options [Hash] Additional options
|
|
262
|
+
# @yield Block containing group items
|
|
263
|
+
def group(**options, &block)
|
|
264
|
+
group_builder = ComboboxListBuilder.new(@view)
|
|
265
|
+
@view.capture(group_builder, &block)
|
|
266
|
+
|
|
267
|
+
@parts << @view.render(
|
|
268
|
+
"components/combobox/group",
|
|
269
|
+
**options
|
|
270
|
+
) { group_builder.to_html }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Adds a label
|
|
274
|
+
#
|
|
275
|
+
# @param text [String, nil] Label text
|
|
276
|
+
# @param options [Hash] Additional options
|
|
277
|
+
# @yield Optional block for custom content
|
|
278
|
+
def label(text = nil, **options, &block)
|
|
279
|
+
@parts << @view.render(
|
|
280
|
+
"components/combobox/label",
|
|
281
|
+
text: text,
|
|
282
|
+
**options,
|
|
283
|
+
&block
|
|
284
|
+
)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Adds a separator
|
|
288
|
+
#
|
|
289
|
+
# @param options [Hash] Additional options
|
|
290
|
+
def separator(**options)
|
|
291
|
+
@parts << @view.render("components/combobox/separator", **options)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Generates the final HTML
|
|
295
|
+
def to_html
|
|
296
|
+
@view.safe_join(@parts)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|