maquina-components 0.3.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.
@@ -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,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,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 %>
@@ -1,3 +1,3 @@
1
1
  module MaquinaComponents
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maquina-components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Alberto Chávez
@@ -51,8 +51,10 @@ files:
51
51
  - app/assets/stylesheets/alert.css
52
52
  - app/assets/stylesheets/badge.css
53
53
  - app/assets/stylesheets/breadcrumbs.css
54
+ - app/assets/stylesheets/calendar.css
54
55
  - app/assets/stylesheets/card.css
55
56
  - app/assets/stylesheets/combobox.css
57
+ - app/assets/stylesheets/date_picker.css
56
58
  - app/assets/stylesheets/dropdown_menu.css
57
59
  - app/assets/stylesheets/empty.css
58
60
  - app/assets/stylesheets/form.css
@@ -65,6 +67,7 @@ files:
65
67
  - app/assets/stylesheets/toggle_group.css
66
68
  - app/assets/tailwind/maquina_components_engine/engine.css
67
69
  - app/helpers/maquina_components/breadcrumbs_helper.rb
70
+ - app/helpers/maquina_components/calendar_helper.rb
68
71
  - app/helpers/maquina_components/combobox_helper.rb
69
72
  - app/helpers/maquina_components/dropdown_menu_helper.rb
70
73
  - app/helpers/maquina_components/empty_helper.rb
@@ -75,7 +78,9 @@ files:
75
78
  - app/helpers/maquina_components/toast_helper.rb
76
79
  - app/helpers/maquina_components/toggle_group_helper.rb
77
80
  - app/javascript/controllers/breadcrumb_controller.js
81
+ - app/javascript/controllers/calendar_controller.js
78
82
  - app/javascript/controllers/combobox_controller.js
83
+ - app/javascript/controllers/date_picker_controller.js
79
84
  - app/javascript/controllers/dropdown_menu_controller.js
80
85
  - app/javascript/controllers/menu_button_controller.js
81
86
  - app/javascript/controllers/sidebar_controller.js
@@ -86,8 +91,10 @@ files:
86
91
  - app/views/components/_alert.html.erb
87
92
  - app/views/components/_badge.html.erb
88
93
  - app/views/components/_breadcrumbs.html.erb
94
+ - app/views/components/_calendar.html.erb
89
95
  - app/views/components/_card.html.erb
90
96
  - app/views/components/_combobox.html.erb
97
+ - app/views/components/_date_picker.html.erb
91
98
  - app/views/components/_dropdown.html.erb
92
99
  - app/views/components/_dropdown_menu.html.erb
93
100
  - app/views/components/_empty.html.erb
@@ -109,6 +116,8 @@ files:
109
116
  - app/views/components/breadcrumbs/_list.html.erb
110
117
  - app/views/components/breadcrumbs/_page.html.erb
111
118
  - app/views/components/breadcrumbs/_separator.html.erb
119
+ - app/views/components/calendar/_header.html.erb
120
+ - app/views/components/calendar/_week.html.erb
112
121
  - app/views/components/card/_action.html.erb
113
122
  - app/views/components/card/_content.html.erb
114
123
  - app/views/components/card/_description.html.erb