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,121 @@
|
|
|
1
|
+
<%# locals: (selected: nil, selected_end: nil, month: nil, year: nil, mode: :single, min_date: nil, max_date: nil, disabled_dates: [], show_outside_days: true, week_starts_on: :sunday, cell_size: nil, input_name: nil, input_name_end: nil, css_classes: "", **html_options) %>
|
|
2
|
+
<%
|
|
3
|
+
# Parse selected dates
|
|
4
|
+
selected_date = case selected
|
|
5
|
+
when String then Date.parse(selected) rescue nil
|
|
6
|
+
when Date, Time, DateTime then selected.to_date
|
|
7
|
+
else nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
selected_end_date = case selected_end
|
|
11
|
+
when String then Date.parse(selected_end) rescue nil
|
|
12
|
+
when Date, Time, DateTime then selected_end.to_date
|
|
13
|
+
else nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Parse min/max dates
|
|
17
|
+
min_date_parsed = case min_date
|
|
18
|
+
when String then Date.parse(min_date) rescue nil
|
|
19
|
+
when Date, Time, DateTime then min_date.to_date
|
|
20
|
+
else nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
max_date_parsed = case max_date
|
|
24
|
+
when String then Date.parse(max_date) rescue nil
|
|
25
|
+
when Date, Time, DateTime then max_date.to_date
|
|
26
|
+
else nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Determine display month/year
|
|
30
|
+
display_date = selected_date || Date.current
|
|
31
|
+
display_month = month || display_date.month
|
|
32
|
+
display_year = year || display_date.year
|
|
33
|
+
|
|
34
|
+
# Build the calendar data
|
|
35
|
+
first_of_month = Date.new(display_year, display_month, 1)
|
|
36
|
+
last_of_month = first_of_month.end_of_month
|
|
37
|
+
|
|
38
|
+
# Calculate start of calendar grid
|
|
39
|
+
week_start = week_starts_on == :monday ? 1 : 0
|
|
40
|
+
days_before = (first_of_month.wday - week_start) % 7
|
|
41
|
+
calendar_start = first_of_month - days_before.days
|
|
42
|
+
|
|
43
|
+
# Calculate end of calendar grid (6 weeks max)
|
|
44
|
+
total_days = days_before + last_of_month.day
|
|
45
|
+
weeks_needed = (total_days / 7.0).ceil
|
|
46
|
+
weeks_needed = [weeks_needed, 6].min
|
|
47
|
+
calendar_end = calendar_start + (weeks_needed * 7 - 1).days
|
|
48
|
+
|
|
49
|
+
# Build weeks array
|
|
50
|
+
weeks = (calendar_start..calendar_end).each_slice(7).to_a
|
|
51
|
+
|
|
52
|
+
# Weekday names
|
|
53
|
+
weekday_names = week_starts_on == :monday ?
|
|
54
|
+
%w[Mo Tu We Th Fr Sa Su] :
|
|
55
|
+
%w[Su Mo Tu We Th Fr Sa]
|
|
56
|
+
|
|
57
|
+
merged_data = (html_options.delete(:data) || {}).merge(
|
|
58
|
+
controller: "calendar",
|
|
59
|
+
component: "calendar",
|
|
60
|
+
"calendar-mode-value": mode,
|
|
61
|
+
"calendar-month-value": display_month,
|
|
62
|
+
"calendar-year-value": display_year,
|
|
63
|
+
"calendar-selected-value": selected_date&.iso8601,
|
|
64
|
+
"calendar-selected-end-value": selected_end_date&.iso8601,
|
|
65
|
+
"calendar-min-date-value": min_date_parsed&.iso8601,
|
|
66
|
+
"calendar-max-date-value": max_date_parsed&.iso8601,
|
|
67
|
+
"calendar-week-starts-on-value": week_starts_on
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
style_attr = cell_size ? "--cell-size: #{cell_size};" : nil
|
|
71
|
+
%>
|
|
72
|
+
|
|
73
|
+
<%= content_tag :div,
|
|
74
|
+
class: css_classes.presence,
|
|
75
|
+
data: merged_data,
|
|
76
|
+
style: style_attr,
|
|
77
|
+
**html_options do %>
|
|
78
|
+
|
|
79
|
+
<%# Hidden inputs for form integration %>
|
|
80
|
+
<% if input_name %>
|
|
81
|
+
<input type="hidden"
|
|
82
|
+
name="<%= input_name %>"
|
|
83
|
+
value="<%= selected_date&.iso8601 %>"
|
|
84
|
+
data-calendar-target="input">
|
|
85
|
+
<% end %>
|
|
86
|
+
<% if input_name_end && mode == :range %>
|
|
87
|
+
<input type="hidden"
|
|
88
|
+
name="<%= input_name_end %>"
|
|
89
|
+
value="<%= selected_end_date&.iso8601 %>"
|
|
90
|
+
data-calendar-target="inputEnd">
|
|
91
|
+
<% end %>
|
|
92
|
+
|
|
93
|
+
<%# Header with navigation %>
|
|
94
|
+
<%= render "components/calendar/header",
|
|
95
|
+
month: display_month,
|
|
96
|
+
year: display_year,
|
|
97
|
+
month_name: I18n.l(first_of_month, format: "%B %Y") %>
|
|
98
|
+
|
|
99
|
+
<%# Weekday headers %>
|
|
100
|
+
<div data-calendar-part="weekdays">
|
|
101
|
+
<% weekday_names.each do |day_name| %>
|
|
102
|
+
<div data-calendar-part="weekday"><%= day_name %></div>
|
|
103
|
+
<% end %>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<%# Calendar grid %>
|
|
107
|
+
<div data-calendar-part="grid" role="grid" aria-label="Calendar">
|
|
108
|
+
<% weeks.each do |week_days| %>
|
|
109
|
+
<%= render "components/calendar/week",
|
|
110
|
+
days: week_days,
|
|
111
|
+
display_month: display_month,
|
|
112
|
+
selected_date: selected_date,
|
|
113
|
+
selected_end_date: selected_end_date,
|
|
114
|
+
mode: mode,
|
|
115
|
+
min_date: min_date_parsed,
|
|
116
|
+
max_date: max_date_parsed,
|
|
117
|
+
disabled_dates: disabled_dates,
|
|
118
|
+
show_outside_days: show_outside_days %>
|
|
119
|
+
<% end %>
|
|
120
|
+
</div>
|
|
121
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%# locals: (id: nil, name: nil, value: nil, placeholder: "Select...", css_classes: "", **html_options) %>
|
|
2
|
+
<% combobox_id = id || "combobox-#{SecureRandom.hex(4)}" %>
|
|
3
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
4
|
+
component: :combobox,
|
|
5
|
+
controller: "combobox",
|
|
6
|
+
combobox_value_value: value,
|
|
7
|
+
combobox_name_value: name,
|
|
8
|
+
combobox_placeholder_value: placeholder
|
|
9
|
+
) %>
|
|
10
|
+
|
|
11
|
+
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
12
|
+
<%= yield combobox_id %>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<%# locals: (selected: nil, selected_end: nil, mode: :single, min_date: nil, max_date: nil, disabled_dates: [], show_outside_days: true, week_starts_on: :sunday, placeholder: nil, input_name: nil, input_name_end: nil, id: nil, disabled: false, required: false, css_classes: "", **html_options) %>
|
|
2
|
+
<%
|
|
3
|
+
# Generate unique ID for popover targeting
|
|
4
|
+
component_id = id || "date-picker-#{SecureRandom.hex(4)}"
|
|
5
|
+
popover_id = "#{component_id}-popover"
|
|
6
|
+
|
|
7
|
+
# Parse selected dates for display
|
|
8
|
+
selected_date = case selected
|
|
9
|
+
when String then Date.parse(selected) rescue nil
|
|
10
|
+
when Date, Time, DateTime then selected.to_date
|
|
11
|
+
else nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
selected_end_date = case selected_end
|
|
15
|
+
when String then Date.parse(selected_end) rescue nil
|
|
16
|
+
when Date, Time, DateTime then selected_end.to_date
|
|
17
|
+
else nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Format display value
|
|
21
|
+
display_value = if mode == :range && selected_date && selected_end_date
|
|
22
|
+
"#{I18n.l(selected_date, format: :short)} - #{I18n.l(selected_end_date, format: :short)}"
|
|
23
|
+
elsif mode == :range && selected_date
|
|
24
|
+
"#{I18n.l(selected_date, format: :short)} - ..."
|
|
25
|
+
elsif selected_date
|
|
26
|
+
I18n.l(selected_date, format: :long)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
default_placeholder = mode == :range ? "Select date range" : "Select date"
|
|
30
|
+
|
|
31
|
+
merged_data = (html_options.delete(:data) || {}).merge(
|
|
32
|
+
controller: "date-picker",
|
|
33
|
+
component: "date-picker",
|
|
34
|
+
"date-picker-mode-value": mode,
|
|
35
|
+
"date-picker-selected-value": selected_date&.iso8601,
|
|
36
|
+
"date-picker-selected-end-value": selected_end_date&.iso8601
|
|
37
|
+
)
|
|
38
|
+
%>
|
|
39
|
+
|
|
40
|
+
<div id="<%= component_id %>"
|
|
41
|
+
class="<%= css_classes.presence %>"
|
|
42
|
+
data-controller="date-picker"
|
|
43
|
+
data-component="date-picker"
|
|
44
|
+
data-date-picker-mode-value="<%= mode %>"
|
|
45
|
+
data-date-picker-selected-value="<%= selected_date&.iso8601 %>"
|
|
46
|
+
data-date-picker-selected-end-value="<%= selected_end_date&.iso8601 %>">
|
|
47
|
+
|
|
48
|
+
<%# Hidden inputs for form submission %>
|
|
49
|
+
<% if input_name %>
|
|
50
|
+
<input type="hidden"
|
|
51
|
+
name="<%= input_name %>"
|
|
52
|
+
value="<%= selected_date&.iso8601 %>"
|
|
53
|
+
data-date-picker-target="input"
|
|
54
|
+
<%= "required" if required %>>
|
|
55
|
+
<% end %>
|
|
56
|
+
<% if input_name_end && mode == :range %>
|
|
57
|
+
<input type="hidden"
|
|
58
|
+
name="<%= input_name_end %>"
|
|
59
|
+
value="<%= selected_end_date&.iso8601 %>"
|
|
60
|
+
data-date-picker-target="inputEnd">
|
|
61
|
+
<% end %>
|
|
62
|
+
|
|
63
|
+
<%# Trigger button %>
|
|
64
|
+
<button type="button"
|
|
65
|
+
popovertarget="<%= popover_id %>"
|
|
66
|
+
data-date-picker-target="trigger"
|
|
67
|
+
data-date-picker-part="trigger"
|
|
68
|
+
<%= "disabled" if disabled %>
|
|
69
|
+
aria-haspopup="dialog"
|
|
70
|
+
aria-expanded="false">
|
|
71
|
+
<%= icon_for :calendar, class: "size-4" %>
|
|
72
|
+
<span data-date-picker-target="display">
|
|
73
|
+
<%= display_value || placeholder || default_placeholder %>
|
|
74
|
+
</span>
|
|
75
|
+
<% unless display_value %>
|
|
76
|
+
<span data-date-picker-part="placeholder-indicator"></span>
|
|
77
|
+
<% end %>
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
<%# Popover with Calendar %>
|
|
81
|
+
<div id="<%= popover_id %>"
|
|
82
|
+
popover
|
|
83
|
+
data-date-picker-target="popover"
|
|
84
|
+
data-date-picker-part="popover"
|
|
85
|
+
role="dialog"
|
|
86
|
+
aria-modal="true"
|
|
87
|
+
aria-label="<%= mode == :range ? 'Date range picker' : 'Date picker' %>">
|
|
88
|
+
<%= render "components/calendar",
|
|
89
|
+
selected: selected_date,
|
|
90
|
+
selected_end: selected_end_date,
|
|
91
|
+
mode: mode,
|
|
92
|
+
min_date: min_date,
|
|
93
|
+
max_date: max_date,
|
|
94
|
+
disabled_dates: disabled_dates,
|
|
95
|
+
show_outside_days: show_outside_days,
|
|
96
|
+
week_starts_on: week_starts_on,
|
|
97
|
+
data: {
|
|
98
|
+
"date-picker-target": "calendar",
|
|
99
|
+
action: "calendar:change->date-picker#handleChange calendar:navigate->date-picker#handleNavigate"
|
|
100
|
+
} %>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<%# locals: (variant: :default, title: nil, description: nil, icon: nil, duration: 5000, dismissible: true, css_classes: "", **html_options) %>
|
|
2
|
+
<%
|
|
3
|
+
# Auto-select icon based on variant if not provided
|
|
4
|
+
default_icons = {
|
|
5
|
+
default: nil,
|
|
6
|
+
success: :circle_check,
|
|
7
|
+
info: :info,
|
|
8
|
+
warning: :triangle_alert,
|
|
9
|
+
error: :circle_x
|
|
10
|
+
}
|
|
11
|
+
toast_icon = icon || default_icons[variant]
|
|
12
|
+
%>
|
|
13
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
14
|
+
component: :toast,
|
|
15
|
+
controller: "toast",
|
|
16
|
+
variant: variant,
|
|
17
|
+
toast_duration_value: duration,
|
|
18
|
+
toast_dismissible_value: dismissible,
|
|
19
|
+
state: "entering"
|
|
20
|
+
) %>
|
|
21
|
+
|
|
22
|
+
<%= content_tag :div,
|
|
23
|
+
role: "alert",
|
|
24
|
+
class: css_classes.presence,
|
|
25
|
+
data: merged_data,
|
|
26
|
+
**html_options do %>
|
|
27
|
+
<% if toast_icon %>
|
|
28
|
+
<div data-toast-part="icon">
|
|
29
|
+
<%= icon_for toast_icon, class: "size-5" %>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
32
|
+
|
|
33
|
+
<div data-toast-part="content">
|
|
34
|
+
<% if title.present? %>
|
|
35
|
+
<%= render "components/toast/title", text: title %>
|
|
36
|
+
<% end %>
|
|
37
|
+
|
|
38
|
+
<% if description.present? %>
|
|
39
|
+
<%= render "components/toast/description", text: description %>
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<%= yield if block_given? %>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<% if dismissible %>
|
|
46
|
+
<button type="button"
|
|
47
|
+
data-toast-part="close"
|
|
48
|
+
data-action="toast#dismiss"
|
|
49
|
+
aria-label="Dismiss notification">
|
|
50
|
+
<%= icon_for :x, class: "size-4" %>
|
|
51
|
+
</button>
|
|
52
|
+
<% end %>
|
|
53
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%# locals: (position: :bottom_right, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
component: :toaster,
|
|
4
|
+
controller: "toaster",
|
|
5
|
+
position: position.to_s.dasherize,
|
|
6
|
+
toaster_target: "container"
|
|
7
|
+
) %>
|
|
8
|
+
|
|
9
|
+
<%= content_tag :div,
|
|
10
|
+
id: "toaster",
|
|
11
|
+
role: "region",
|
|
12
|
+
aria: { label: "Notifications", live: "polite" },
|
|
13
|
+
class: css_classes.presence,
|
|
14
|
+
data: merged_data,
|
|
15
|
+
**html_options do %>
|
|
16
|
+
<%= yield if block_given? %>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<%# locals: (month:, year:, month_name:, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge("calendar-part": "header") %>
|
|
3
|
+
|
|
4
|
+
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
5
|
+
<button type="button"
|
|
6
|
+
data-action="click->calendar#previousMonth"
|
|
7
|
+
data-calendar-target="prevButton"
|
|
8
|
+
aria-label="Previous month">
|
|
9
|
+
<%= icon_for :chevron_left, class: "size-4" %>
|
|
10
|
+
</button>
|
|
11
|
+
|
|
12
|
+
<div data-calendar-part="caption">
|
|
13
|
+
<%= month_name %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<button type="button"
|
|
17
|
+
data-action="click->calendar#nextMonth"
|
|
18
|
+
data-calendar-target="nextButton"
|
|
19
|
+
aria-label="Next month">
|
|
20
|
+
<%= icon_for :chevron_right, class: "size-4" %>
|
|
21
|
+
</button>
|
|
22
|
+
<% end %>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<%# locals: (days:, display_month:, selected_date: nil, selected_end_date: nil, mode: :single, min_date: nil, max_date: nil, disabled_dates: [], show_outside_days: true, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge("calendar-part": "week") %>
|
|
3
|
+
|
|
4
|
+
<%= content_tag :div, role: "row", class: css_classes.presence, data: merged_data, **html_options do %>
|
|
5
|
+
<% days.each do |day| %>
|
|
6
|
+
<%
|
|
7
|
+
is_outside = day.month != display_month
|
|
8
|
+
is_today = day == Date.current
|
|
9
|
+
is_selected = selected_date && day == selected_date
|
|
10
|
+
is_range_end = selected_end_date && day == selected_end_date
|
|
11
|
+
is_range_middle = selected_date && selected_end_date &&
|
|
12
|
+
day > selected_date && day < selected_end_date
|
|
13
|
+
is_disabled = (min_date && day < min_date) ||
|
|
14
|
+
(max_date && day > max_date) ||
|
|
15
|
+
disabled_dates.include?(day)
|
|
16
|
+
|
|
17
|
+
day_state = if is_selected && mode == :range && selected_end_date
|
|
18
|
+
"range-start"
|
|
19
|
+
elsif is_range_end
|
|
20
|
+
"range-end"
|
|
21
|
+
elsif is_range_middle
|
|
22
|
+
"range-middle"
|
|
23
|
+
elsif is_selected
|
|
24
|
+
"selected"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Skip rendering if outside day and not showing them
|
|
28
|
+
next if is_outside && !show_outside_days
|
|
29
|
+
|
|
30
|
+
day_data = {
|
|
31
|
+
"calendar-part": "day",
|
|
32
|
+
"calendar-target": "day",
|
|
33
|
+
date: day.iso8601,
|
|
34
|
+
state: day_state.presence,
|
|
35
|
+
outside: (is_outside ? true : nil),
|
|
36
|
+
today: (is_today ? true : nil),
|
|
37
|
+
action: "click->calendar#selectDay keydown->calendar#handleKeydown"
|
|
38
|
+
}.compact
|
|
39
|
+
%>
|
|
40
|
+
<button type="button"
|
|
41
|
+
data-calendar-part="day"
|
|
42
|
+
data-calendar-target="day"
|
|
43
|
+
data-date="<%= day.iso8601 %>"
|
|
44
|
+
<%= "data-state=\"#{day_state}\"" if day_state %>
|
|
45
|
+
<%= "data-outside" if is_outside %>
|
|
46
|
+
<%= "data-today" if is_today %>
|
|
47
|
+
data-action="click->calendar#selectDay keydown->calendar#handleKeydown"
|
|
48
|
+
<%= "disabled" if is_disabled %>
|
|
49
|
+
tabindex="<%= is_today ? '0' : '-1' %>"
|
|
50
|
+
<%= "aria-selected=\"true\"" if day_state&.include?("selected") || day_state == "range-start" || day_state == "range-end" %>
|
|
51
|
+
<%= "aria-current=\"date\"" if is_today %>><%= day.day %></button>
|
|
52
|
+
<% end %>
|
|
53
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%# locals: (id:, align: :start, width: :default, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
combobox_part: "content",
|
|
4
|
+
combobox_target: "content",
|
|
5
|
+
align: align,
|
|
6
|
+
width: width
|
|
7
|
+
) %>
|
|
8
|
+
|
|
9
|
+
<%= content_tag :div,
|
|
10
|
+
id: id,
|
|
11
|
+
popover: "auto",
|
|
12
|
+
role: "listbox",
|
|
13
|
+
class: css_classes.presence,
|
|
14
|
+
data: merged_data,
|
|
15
|
+
**html_options do %>
|
|
16
|
+
<%= yield %>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%# locals: (text: "No results found.", css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
combobox_part: "empty",
|
|
4
|
+
combobox_target: "empty"
|
|
5
|
+
) %>
|
|
6
|
+
|
|
7
|
+
<%= content_tag :div, class: css_classes.presence, data: merged_data, hidden: true, **html_options do %>
|
|
8
|
+
<%= text %>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<%# locals: (css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
combobox_part: "group"
|
|
4
|
+
) %>
|
|
5
|
+
|
|
6
|
+
<%= content_tag :div, role: "group", class: css_classes.presence, data: merged_data, **html_options do %>
|
|
7
|
+
<%= yield %>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<%# locals: (placeholder: "Search...", css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
combobox_part: "input",
|
|
4
|
+
combobox_target: "input",
|
|
5
|
+
action: "input->combobox#filter"
|
|
6
|
+
) %>
|
|
7
|
+
|
|
8
|
+
<div data-combobox-part="input-wrapper">
|
|
9
|
+
<%= icon_for :search, class: "size-4 shrink-0 opacity-50" %>
|
|
10
|
+
<%= tag.input type: "text",
|
|
11
|
+
placeholder: placeholder,
|
|
12
|
+
autocomplete: "off",
|
|
13
|
+
autocorrect: "off",
|
|
14
|
+
spellcheck: false,
|
|
15
|
+
class: css_classes.presence,
|
|
16
|
+
data: merged_data,
|
|
17
|
+
**html_options %>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<%# locals: (text: nil, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
combobox_part: "label"
|
|
4
|
+
) %>
|
|
5
|
+
|
|
6
|
+
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
7
|
+
<%= text || yield %>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<%# locals: (value:, selected: false, disabled: false, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
combobox_part: "option",
|
|
4
|
+
combobox_target: "option",
|
|
5
|
+
value: value,
|
|
6
|
+
selected: selected,
|
|
7
|
+
action: "click->combobox#select"
|
|
8
|
+
) %>
|
|
9
|
+
|
|
10
|
+
<% aria_attrs = { selected: selected } %>
|
|
11
|
+
<% aria_attrs[:disabled] = true if disabled %>
|
|
12
|
+
|
|
13
|
+
<%= content_tag :div,
|
|
14
|
+
role: "option",
|
|
15
|
+
tabindex: "-1",
|
|
16
|
+
aria: aria_attrs,
|
|
17
|
+
class: css_classes.presence,
|
|
18
|
+
data: merged_data,
|
|
19
|
+
**html_options do %>
|
|
20
|
+
<span data-combobox-part="check" class="<%= 'invisible' unless selected %>">
|
|
21
|
+
<%= icon_for :check, class: "size-4" %>
|
|
22
|
+
</span>
|
|
23
|
+
<%= yield %>
|
|
24
|
+
<% end %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<%# locals: (for_id:, placeholder: "Select...", variant: :outline, size: :default, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
component: :button,
|
|
4
|
+
variant: variant,
|
|
5
|
+
size: size,
|
|
6
|
+
combobox_part: "trigger",
|
|
7
|
+
combobox_target: "trigger",
|
|
8
|
+
action: "combobox#toggle"
|
|
9
|
+
) %>
|
|
10
|
+
|
|
11
|
+
<%= content_tag :button,
|
|
12
|
+
type: "button",
|
|
13
|
+
popovertarget: for_id,
|
|
14
|
+
popovertargetaction: "toggle",
|
|
15
|
+
role: "combobox",
|
|
16
|
+
aria: { expanded: false, haspopup: "listbox", controls: for_id },
|
|
17
|
+
class: css_classes.presence,
|
|
18
|
+
data: merged_data,
|
|
19
|
+
**html_options do %>
|
|
20
|
+
<span data-combobox-target="label"><%= placeholder %></span>
|
|
21
|
+
<%= icon_for :chevrons_up_down, class: "size-4 shrink-0 opacity-50" %>
|
|
22
|
+
<% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%# locals: (label:, href: nil, method: nil, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
toast_part: "action"
|
|
4
|
+
) %>
|
|
5
|
+
|
|
6
|
+
<% merged_data["turbo-method"] = method if method.present? %>
|
|
7
|
+
|
|
8
|
+
<% if href.present? %>
|
|
9
|
+
<%= link_to label, href, class: css_classes.presence, data: merged_data, **html_options %>
|
|
10
|
+
<% else %>
|
|
11
|
+
<%= content_tag :button, type: "button", class: css_classes.presence, data: merged_data, **html_options do %>
|
|
12
|
+
<%= label %>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<%# locals: (text: nil, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
toast_part: "description"
|
|
4
|
+
) %>
|
|
5
|
+
|
|
6
|
+
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
7
|
+
<%= text || yield %>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<%# locals: (text: nil, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = (html_options.delete(:data) || {}).merge(
|
|
3
|
+
toast_part: "title"
|
|
4
|
+
) %>
|
|
5
|
+
|
|
6
|
+
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
7
|
+
<%= text || yield %>
|
|
8
|
+
<% end %>
|