maquina-components 0.7.4 → 0.8.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/app/assets/stylesheets/calendar.css +48 -1
- data/app/assets/stylesheets/date_picker.css +13 -1
- data/app/helpers/maquina_components/calendar_helper.rb +37 -0
- data/app/javascript/controllers/calendar_controller.js +91 -8
- data/app/views/components/_calendar.html.erb +27 -3
- data/app/views/components/_date_picker.html.erb +3 -1
- data/app/views/components/calendar/_header.html.erb +28 -4
- data/lib/maquina_components/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95885780d4ac7c947e4561e821d4c3cb2f985a3a363738f7cdc7fc01a858fc11
|
|
4
|
+
data.tar.gz: 35100ad72833ef5be786a40444f5af15c37866d0dc8dce4dee34232f04bfe0a5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1e7d220b352af1cd2e9fabf11902165c2d7d958608ea504f0b6a896fef336c3c8cd1e556090ab2db7dfa54c8812249fc5611c1a19820701c37c015c15b82a10
|
|
7
|
+
data.tar.gz: 347641b6a36dac6f72f9344e8e6af0a81f0c80bf29dd1ce022e20db9a295db2141d540eb16d41cea92fc19f4a59345438712f9b958c802b3cc1f109bd2d5abbb
|
|
@@ -7,15 +7,24 @@
|
|
|
7
7
|
* Structure:
|
|
8
8
|
* - calendar (root container)
|
|
9
9
|
* - header (navigation)
|
|
10
|
+
* - caption, or with caption_layout: :dropdown
|
|
11
|
+
* dropdowns > month-select, year-select (native [data-component="select"])
|
|
10
12
|
* - weekdays (day name headers)
|
|
11
13
|
* - grid (day buttons grid)
|
|
12
14
|
* - week (row)
|
|
13
15
|
* - day (button)
|
|
16
|
+
* - footer (optional, the partial's block)
|
|
17
|
+
*
|
|
18
|
+
* Inside a popover the controller sets data-side="top|bottom" on the root.
|
|
19
|
+
* Flipped above its trigger, a popover is pinned at its bottom edge while
|
|
20
|
+
* months change height (4-6 weeks), so [data-side="top"] moves the header
|
|
21
|
+
* last — next to the trigger, where the arrows stay put.
|
|
14
22
|
*
|
|
15
23
|
* Cascade order in this file (full contract documented in form.css):
|
|
16
24
|
* 1. base [data-component="calendar"], [data-calendar-part="…"]
|
|
17
25
|
* 2. sizes — none (cell metrics come from --cell-size)
|
|
18
|
-
* 3. variants [data-calendar-part="day"]:where([data-today], [data-state="…"], [data-outside])
|
|
26
|
+
* 3. variants [data-calendar-part="day"]:where([data-today], [data-state="…"], [data-outside]),
|
|
27
|
+
* [data-component="calendar"]:where([data-side="top"])
|
|
19
28
|
* 4. states […]:where(:hover, :focus-visible, :disabled)
|
|
20
29
|
* 5. dark — none; dark-mode differences are tokens (see tokens.css)
|
|
21
30
|
* Every rule is flattened to 0,1,0 with :where() and lives in
|
|
@@ -94,6 +103,26 @@
|
|
|
94
103
|
color: var(--foreground);
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
/* ===== Caption dropdowns =====
|
|
107
|
+
Month and year are native selects styled by form.css (colors, radius,
|
|
108
|
+
focus, chevron, dark mode). Here they only shrink to fit beside the
|
|
109
|
+
arrows in a 7-cell header: calendar.css loads after form.css, so these
|
|
110
|
+
0,1,0 rules win on source order. */
|
|
111
|
+
[data-calendar-part="dropdowns"] {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
@apply gap-1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
:where([data-calendar-part="dropdowns"]) [data-component="select"] {
|
|
118
|
+
width: auto;
|
|
119
|
+
height: var(--cell-size);
|
|
120
|
+
@apply pl-2 pr-6 py-0 text-sm;
|
|
121
|
+
font-weight: var(--label-weight, 500);
|
|
122
|
+
background-position: right 0.25rem center;
|
|
123
|
+
background-size: 1rem 1rem;
|
|
124
|
+
}
|
|
125
|
+
|
|
97
126
|
/* ===== Weekday Headers ===== */
|
|
98
127
|
[data-calendar-part="weekdays"] {
|
|
99
128
|
display: grid;
|
|
@@ -123,6 +152,11 @@
|
|
|
123
152
|
grid-template-columns: repeat(7, var(--cell-size));
|
|
124
153
|
}
|
|
125
154
|
|
|
155
|
+
/* ===== Footer ===== */
|
|
156
|
+
[data-calendar-part="footer"] {
|
|
157
|
+
@apply mt-4;
|
|
158
|
+
}
|
|
159
|
+
|
|
126
160
|
/* ===== Day Button Base ===== */
|
|
127
161
|
[data-calendar-part="day"] {
|
|
128
162
|
display: inline-flex;
|
|
@@ -205,6 +239,19 @@
|
|
|
205
239
|
@apply opacity-30;
|
|
206
240
|
}
|
|
207
241
|
|
|
242
|
+
/* ===== Popover side ===== */
|
|
243
|
+
|
|
244
|
+
/* Opened above the trigger: weekdays, grid, footer, then the header */
|
|
245
|
+
[data-component="calendar"]:where([data-side="top"]) {
|
|
246
|
+
display: flex;
|
|
247
|
+
flex-direction: column;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
:where([data-component="calendar"][data-side="top"]) [data-calendar-part="header"] {
|
|
251
|
+
order: 1;
|
|
252
|
+
@apply mt-4 mb-0;
|
|
253
|
+
}
|
|
254
|
+
|
|
208
255
|
/* ===== Day States ===== */
|
|
209
256
|
|
|
210
257
|
/* Hover — only for cells with no selection state of their own */
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* 2. sizes :where([data-size="sm"|"lg"]) [data-date-picker-part="trigger"]
|
|
15
15
|
* 3. variants :where([data-full-width]) …
|
|
16
16
|
* 4. states […]:where(:hover, :focus-visible, :disabled, [aria-expanded],
|
|
17
|
-
* :has(input:invalid))
|
|
17
|
+
* :has(input:invalid)); popover open/closed, and
|
|
18
|
+
* :has(calendar[data-side="top"]) for the animation
|
|
18
19
|
* 5. dark — none; dark-mode differences are tokens (see tokens.css)
|
|
19
20
|
* Every rule is flattened to 0,1,0 with :where() and lives in
|
|
20
21
|
* @layer components, so a caller's utility class wins and source order is
|
|
@@ -160,6 +161,17 @@
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
/* Opened above the trigger (the calendar's data-side): grow upward out of it */
|
|
165
|
+
[data-date-picker-part="popover"]:where(:not(:popover-open):has([data-component="calendar"][data-side="top"])) {
|
|
166
|
+
transform: translateY(0.5rem) scale(0.95);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@starting-style {
|
|
170
|
+
[data-date-picker-part="popover"]:where(:popover-open:has([data-component="calendar"][data-side="top"])) {
|
|
171
|
+
transform: translateY(0.5rem) scale(0.95);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
163
175
|
/* Backdrop */
|
|
164
176
|
[data-date-picker-part="popover"]::backdrop {
|
|
165
177
|
background-color: transparent;
|
|
@@ -72,6 +72,43 @@ module MaquinaComponents
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
# Years offered by the caption's year select (caption_layout: :dropdown)
|
|
76
|
+
#
|
|
77
|
+
# min_date/max_date set the bounds when present; otherwise years applies,
|
|
78
|
+
# 100 years back to 10 ahead by default. Given both, the narrower bound
|
|
79
|
+
# wins. The displayed year is always included.
|
|
80
|
+
#
|
|
81
|
+
# @param display_year [Integer] Year the calendar shows
|
|
82
|
+
# @param min_date [Date, nil] Minimum selectable date
|
|
83
|
+
# @param max_date [Date, nil] Maximum selectable date
|
|
84
|
+
# @param years [Range, nil] Explicit span of years
|
|
85
|
+
# @return [Range]
|
|
86
|
+
def calendar_year_range(display_year, min_date: nil, max_date: nil, years: nil)
|
|
87
|
+
first, last = (years || ((Date.current.year - 100)..(Date.current.year + 10))).minmax
|
|
88
|
+
first = min_date.year if min_date && (years.nil? || min_date.year > first)
|
|
89
|
+
last = max_date.year if max_date && (years.nil? || max_date.year < last)
|
|
90
|
+
|
|
91
|
+
[first, display_year].min..[last, display_year].max
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Options for the caption's month select: abbreviated localized names
|
|
95
|
+
# (the full ones do not fit beside the year in a 7-cell header), with the
|
|
96
|
+
# months wholly outside min_date/max_date disabled for that year
|
|
97
|
+
#
|
|
98
|
+
# @param year [Integer] Year the months belong to
|
|
99
|
+
# @param min_date [Date, nil] Minimum selectable date
|
|
100
|
+
# @param max_date [Date, nil] Maximum selectable date
|
|
101
|
+
# @return [Array<Array(String, Integer, Boolean)>] [name, month, disabled]
|
|
102
|
+
def calendar_month_options(year, min_date: nil, max_date: nil)
|
|
103
|
+
I18n.t("date.abbr_month_names").drop(1).each_with_index.map do |name, index|
|
|
104
|
+
first_of_month = Date.new(year, index + 1, 1)
|
|
105
|
+
disabled = (min_date && first_of_month.end_of_month < min_date) ||
|
|
106
|
+
(max_date && first_of_month > max_date)
|
|
107
|
+
|
|
108
|
+
[name, index + 1, !!disabled]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
75
112
|
# Check if a date falls within a range
|
|
76
113
|
#
|
|
77
114
|
# @param date [Date] The date to check
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = ["day", "input", "inputEnd", "prevButton", "nextButton", "grid", "caption"]
|
|
4
|
+
static targets = ["day", "input", "inputEnd", "prevButton", "nextButton", "grid", "caption", "monthSelect", "yearSelect"]
|
|
5
5
|
|
|
6
6
|
static values = {
|
|
7
7
|
month: Number,
|
|
@@ -13,12 +13,43 @@ export default class extends Controller {
|
|
|
13
13
|
mode: { type: String, default: "single" },
|
|
14
14
|
weekStartsOn: { type: String, default: "sunday" },
|
|
15
15
|
disabledDates: Array,
|
|
16
|
-
showOutsideDays: { type: Boolean, default: true }
|
|
16
|
+
showOutsideDays: { type: Boolean, default: true },
|
|
17
|
+
// Bounds of the caption's year select (caption_layout: :dropdown only)
|
|
18
|
+
startYear: Number,
|
|
19
|
+
endYear: Number
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
connect() {
|
|
20
23
|
this.updateNavigationState()
|
|
21
24
|
this.updateDayStates()
|
|
25
|
+
|
|
26
|
+
// Inside a popover (date picker or a caller's own), track which side of
|
|
27
|
+
// the trigger it opened on, so the header can sit next to the trigger
|
|
28
|
+
this.popover = this.element.closest("[popover]")
|
|
29
|
+
if (this.popover) {
|
|
30
|
+
this.boundUpdateSide = this.updateSide.bind(this)
|
|
31
|
+
this.popover.addEventListener("toggle", this.boundUpdateSide)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnect() {
|
|
36
|
+
this.popover?.removeEventListener("toggle", this.boundUpdateSide)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sets data-side="top|bottom" when the popover is open. A popover flipped
|
|
41
|
+
* above its trigger is pinned at its bottom edge, so CSS moves the header
|
|
42
|
+
* there: month heights differ, and the arrows must not move under the cursor.
|
|
43
|
+
*/
|
|
44
|
+
updateSide() {
|
|
45
|
+
if (!this.popover?.matches(":popover-open")) return
|
|
46
|
+
|
|
47
|
+
const trigger = this.popover.id &&
|
|
48
|
+
document.querySelector(`[popovertarget="${CSS.escape(this.popover.id)}"]`)
|
|
49
|
+
if (!trigger) return
|
|
50
|
+
|
|
51
|
+
const above = this.popover.getBoundingClientRect().top < trigger.getBoundingClientRect().top
|
|
52
|
+
this.element.dataset.side = above ? "top" : "bottom"
|
|
22
53
|
}
|
|
23
54
|
|
|
24
55
|
previousMonth() {
|
|
@@ -45,12 +76,38 @@ export default class extends Controller {
|
|
|
45
76
|
this.navigateToMonth(newMonth, newYear)
|
|
46
77
|
}
|
|
47
78
|
|
|
79
|
+
// Caption dropdowns (caption_layout: :dropdown)
|
|
80
|
+
selectMonth() {
|
|
81
|
+
this.navigateToMonth(Number(this.monthSelectTarget.value), this.yearValue)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
selectYear() {
|
|
85
|
+
const year = Number(this.yearSelectTarget.value)
|
|
86
|
+
this.navigateToMonth(this.clampMonth(this.monthValue, year), year)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// A year change keeps the month, unless min/max date rule it out that year
|
|
90
|
+
clampMonth(month, year) {
|
|
91
|
+
const [minYear, minMonth] = (this.minDateValue || "").split("-").map(Number)
|
|
92
|
+
const [maxYear, maxMonth] = (this.maxDateValue || "").split("-").map(Number)
|
|
93
|
+
|
|
94
|
+
if (year === minYear && month < minMonth) return minMonth
|
|
95
|
+
if (year === maxYear && month > maxMonth) return maxMonth
|
|
96
|
+
return month
|
|
97
|
+
}
|
|
98
|
+
|
|
48
99
|
navigateToMonth(month, year) {
|
|
100
|
+
// The year select lists only startYear..endYear; never show a month it can't
|
|
101
|
+
if ((this.hasStartYearValue && year < this.startYearValue) ||
|
|
102
|
+
(this.hasEndYearValue && year > this.endYearValue)) return
|
|
103
|
+
|
|
49
104
|
this.monthValue = month
|
|
50
105
|
this.yearValue = year
|
|
51
106
|
|
|
52
107
|
this.rebuildCalendar()
|
|
108
|
+
this.syncDropdowns()
|
|
53
109
|
this.updateNavigationState()
|
|
110
|
+
this.updateSide()
|
|
54
111
|
|
|
55
112
|
this.dispatch("navigate", {
|
|
56
113
|
detail: {
|
|
@@ -337,14 +394,40 @@ export default class extends Controller {
|
|
|
337
394
|
}
|
|
338
395
|
|
|
339
396
|
updateNavigationState() {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
397
|
+
const firstOfMonth = this.formatDate(new Date(this.yearValue, this.monthValue - 1, 1))
|
|
398
|
+
const lastOfMonth = this.formatDate(new Date(this.yearValue, this.monthValue, 0))
|
|
399
|
+
|
|
400
|
+
if (this.hasPrevButtonTarget) {
|
|
401
|
+
this.prevButtonTarget.disabled = Boolean(
|
|
402
|
+
(this.minDateValue && firstOfMonth <= this.minDateValue) ||
|
|
403
|
+
(this.hasStartYearValue && this.monthValue === 1 && this.yearValue <= this.startYearValue)
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (this.hasNextButtonTarget) {
|
|
408
|
+
this.nextButtonTarget.disabled = Boolean(
|
|
409
|
+
(this.maxDateValue && lastOfMonth >= this.maxDateValue) ||
|
|
410
|
+
(this.hasEndYearValue && this.monthValue === 12 && this.yearValue >= this.endYearValue)
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Mirrors calendar_month_options: a month wholly outside min/max is disabled
|
|
416
|
+
syncDropdowns() {
|
|
417
|
+
if (this.hasMonthSelectTarget) {
|
|
418
|
+
for (const option of this.monthSelectTarget.options) {
|
|
419
|
+
const month = Number(option.value)
|
|
420
|
+
const first = this.formatDate(new Date(this.yearValue, month - 1, 1))
|
|
421
|
+
const last = this.formatDate(new Date(this.yearValue, month, 0))
|
|
422
|
+
|
|
423
|
+
option.disabled = Boolean((this.minDateValue && last < this.minDateValue) ||
|
|
424
|
+
(this.maxDateValue && first > this.maxDateValue))
|
|
425
|
+
}
|
|
426
|
+
this.monthSelectTarget.value = String(this.monthValue)
|
|
343
427
|
}
|
|
344
428
|
|
|
345
|
-
if (this.
|
|
346
|
-
|
|
347
|
-
this.nextButtonTarget.disabled = lastOfMonth >= this.maxDateValue
|
|
429
|
+
if (this.hasYearSelectTarget) {
|
|
430
|
+
this.yearSelectTarget.value = String(this.yearValue)
|
|
348
431
|
}
|
|
349
432
|
}
|
|
350
433
|
|
|
@@ -1,4 +1,4 @@
|
|
|
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) %>
|
|
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, caption_layout: :label, years: nil, cell_size: nil, input_name: nil, input_name_end: nil, css_classes: "", **html_options) %>
|
|
2
2
|
<%
|
|
3
3
|
# Dates may arrive as Date, Time or ISO strings
|
|
4
4
|
selected_date = calendar_to_date(selected)
|
|
@@ -36,6 +36,18 @@
|
|
|
36
36
|
abbr_day_names.rotate(1) :
|
|
37
37
|
abbr_day_names
|
|
38
38
|
|
|
39
|
+
# caption_layout: :dropdown swaps the caption for month and year selects.
|
|
40
|
+
# Their locals reach the header only then, so an app's own calendar/header
|
|
41
|
+
# override written for the caption keeps working.
|
|
42
|
+
dropdown = caption_layout.to_sym == :dropdown
|
|
43
|
+
year_range = calendar_year_range(display_year, min_date: min_date_parsed, max_date: max_date_parsed, years: years) if dropdown
|
|
44
|
+
header_locals = if dropdown
|
|
45
|
+
{caption_layout: :dropdown, years: year_range,
|
|
46
|
+
month_options: calendar_month_options(display_year, min_date: min_date_parsed, max_date: max_date_parsed)}
|
|
47
|
+
else
|
|
48
|
+
{}
|
|
49
|
+
end
|
|
50
|
+
|
|
39
51
|
merged_data = merge_component_data(html_options,
|
|
40
52
|
controller: "calendar",
|
|
41
53
|
component: "calendar",
|
|
@@ -48,7 +60,9 @@
|
|
|
48
60
|
"calendar-max-date-value": max_date_parsed&.iso8601,
|
|
49
61
|
"calendar-week-starts-on-value": week_starts_on,
|
|
50
62
|
"calendar-disabled-dates-value": disabled_dates_parsed.map(&:iso8601).presence,
|
|
51
|
-
"calendar-show-outside-days-value": show_outside_days
|
|
63
|
+
"calendar-show-outside-days-value": show_outside_days,
|
|
64
|
+
"calendar-start-year-value": year_range&.first,
|
|
65
|
+
"calendar-end-year-value": year_range&.last
|
|
52
66
|
)
|
|
53
67
|
|
|
54
68
|
# cell_size and a caller's style: both write the style attribute, so join them
|
|
@@ -80,7 +94,8 @@
|
|
|
80
94
|
<%= render "components/calendar/header",
|
|
81
95
|
month: display_month,
|
|
82
96
|
year: display_year,
|
|
83
|
-
month_name: I18n.l(first_of_month, format: "%B %Y")
|
|
97
|
+
month_name: I18n.l(first_of_month, format: "%B %Y"),
|
|
98
|
+
**header_locals %>
|
|
84
99
|
|
|
85
100
|
<%# Weekday headers %>
|
|
86
101
|
<div data-calendar-part="weekdays">
|
|
@@ -104,4 +119,13 @@
|
|
|
104
119
|
show_outside_days: show_outside_days %>
|
|
105
120
|
<% end %>
|
|
106
121
|
</div>
|
|
122
|
+
|
|
123
|
+
<%# Optional footer (actions such as Clear/Done); stays between the grid and
|
|
124
|
+
a header moved to the bottom when the calendar's popover opens above.
|
|
125
|
+
Without a caller block a partial's yield returns content_for(:layout),
|
|
126
|
+
which is the whole page when rendered from a layout, so that is no footer. %>
|
|
127
|
+
<% footer = capture { yield }.presence %>
|
|
128
|
+
<% if footer && footer != content_for(:layout) %>
|
|
129
|
+
<div data-calendar-part="footer"><%= footer %></div>
|
|
130
|
+
<% end %>
|
|
107
131
|
<% end %>
|
|
@@ -1,4 +1,4 @@
|
|
|
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, size: :default, full_width: false, css_classes: "", **html_options) %>
|
|
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, caption_layout: :label, years: nil, placeholder: nil, input_name: nil, input_name_end: nil, id: nil, disabled: false, required: false, size: :default, full_width: false, css_classes: "", **html_options) %>
|
|
2
2
|
<%
|
|
3
3
|
# Deterministic ID for popover targeting: random ids break idiomorph
|
|
4
4
|
# matching across Turbo morphs
|
|
@@ -81,6 +81,8 @@
|
|
|
81
81
|
disabled_dates: disabled_dates,
|
|
82
82
|
show_outside_days: show_outside_days,
|
|
83
83
|
week_starts_on: week_starts_on,
|
|
84
|
+
caption_layout: caption_layout,
|
|
85
|
+
years: years,
|
|
84
86
|
data: {
|
|
85
87
|
"date-picker-target": "calendar",
|
|
86
88
|
action: "calendar:change->date-picker#handleChange calendar:navigate->date-picker#handleNavigate"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%# locals: (month:, year:, month_name:, css_classes: "", **html_options) %>
|
|
1
|
+
<%# locals: (month:, year:, month_name:, caption_layout: :label, years: nil, month_options: nil, css_classes: "", **html_options) %>
|
|
2
2
|
<% merged_data = merge_component_data(html_options, "calendar-part": "header") %>
|
|
3
3
|
|
|
4
4
|
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
@@ -9,9 +9,33 @@
|
|
|
9
9
|
<%= builtin_icon_for :chevron_left, class: "size-4" %>
|
|
10
10
|
</button>
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
<% if caption_layout.to_sym == :dropdown %>
|
|
13
|
+
<div data-calendar-part="dropdowns">
|
|
14
|
+
<select data-component="select"
|
|
15
|
+
data-calendar-part="month-select"
|
|
16
|
+
data-calendar-target="monthSelect"
|
|
17
|
+
data-action="change->calendar#selectMonth"
|
|
18
|
+
aria-label="<%= t("maquina_components.calendar.month", default: "Month") %>">
|
|
19
|
+
<% month_options.each do |name, value, disabled| %>
|
|
20
|
+
<%= tag.option name, value: value, selected: value == month, disabled: disabled %>
|
|
21
|
+
<% end %>
|
|
22
|
+
</select>
|
|
23
|
+
|
|
24
|
+
<select data-component="select"
|
|
25
|
+
data-calendar-part="year-select"
|
|
26
|
+
data-calendar-target="yearSelect"
|
|
27
|
+
data-action="change->calendar#selectYear"
|
|
28
|
+
aria-label="<%= t("maquina_components.calendar.year", default: "Year") %>">
|
|
29
|
+
<% years.each do |value| %>
|
|
30
|
+
<%= tag.option value, value: value, selected: value == year %>
|
|
31
|
+
<% end %>
|
|
32
|
+
</select>
|
|
33
|
+
</div>
|
|
34
|
+
<% else %>
|
|
35
|
+
<div data-calendar-part="caption">
|
|
36
|
+
<%= month_name %>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
15
39
|
|
|
16
40
|
<button type="button"
|
|
17
41
|
data-action="click->calendar#nextMonth"
|