Files
MujicaUI/month_picker.go
T

111 lines
3.4 KiB
Go

package mujica
import (
"fmt"
"log/slog"
"time"
"github.com/egoist/mygo/ui"
)
// YearMonth is a month of a year. The zero value means no month.
type YearMonth struct {
Year int
Month time.Month
}
// IsZero reports whether m is the empty month.
func (m YearMonth) IsZero() bool { return m == YearMonth{} }
// String formats m as 2006-01, or "" when empty.
func (m YearMonth) String() string {
if m.IsZero() {
return ""
}
return fmt.Sprintf("%04d-%02d", m.Year, m.Month)
}
func (m YearMonth) ordinal() int { return m.Year*12 + int(m.Month) - 1 }
func yearMonthOf(o int) YearMonth {
y := o / 12
if o < 0 && o%12 != 0 {
y--
}
return YearMonth{y, time.Month(o-y*12) + 1}
}
// MonthPickerOptions configure a MonthPicker.
type MonthPickerOptions struct {
// Min and Max bound the months chosen; zero leaves a side open.
Min, Max YearMonth
DisableMonth func(YearMonth) bool
Clearable bool
// Today marks the current month; zero uses the system clock.
Today Date
Disabled bool
// Label names the field for assistive technology.
Label string
}
// MonthPicker chooses a month into *value from a year page; PageUp and PageDown change the year.
func MonthPicker(c *ui.Context, value *YearMonth, opts MonthPickerOptions) DateTimeResult {
for _, m := range []YearMonth{*value, opts.Min, opts.Max} {
if !m.IsZero() && (m.Month < time.January || m.Month > time.December) {
panic(fmt.Sprintf("mujica: MonthPicker month %v is not valid", m))
}
}
if !opts.Min.IsZero() && !opts.Max.IsZero() && opts.Min.ordinal() > opts.Max.ordinal() {
panic(fmt.Sprintf("mujica: MonthPicker Min %v is after Max %v", opts.Min, opts.Max))
}
today := datetimeToday(c, opts.Today)
now := YearMonth{today.Year, today.Month}
title := func(m YearMonth) string { return datetimeMonthTitle(c, m.Year, m.Month) }
home := *value
if home.IsZero() {
home = now
}
text := ""
if !value.IsZero() {
text = title(*value)
}
label := opts.Label
if label == "" {
label = msg(c, "month_picker.placeholder")
}
col, picked, ok, cleared := datetimeGridPicker(c, datetimeGridField{
text: text, placeholder: msg(c, "month_picker.placeholder"), label: label,
clearable: opts.Clearable, hasValue: !value.IsZero(), disabled: opts.Disabled, home: home.ordinal(),
}, datetimeGridConfig{
pageOf: func(o int) (int, int) { return yearMonthOf(o).Year * 12, 12 },
cols: 3,
page: 12,
title: func(start int) string { return fmt.Sprintf(msg(c, "month_picker.year"), yearMonthOf(start).Year) },
prev: msg(c, "calendar.prev_year"),
next: msg(c, "calendar.next_year"),
text: func(o int) string { return datetimeMonthName(c, yearMonthOf(o).Month) },
label: func(o int) string { return title(yearMonthOf(o)) },
chosen: func(o int) bool { return !value.IsZero() && o == value.ordinal() },
current: func(o int) bool { return o == now.ordinal() },
enabled: func(o int) bool {
m := yearMonthOf(o)
return (opts.Min.IsZero() || o >= opts.Min.ordinal()) && (opts.Max.IsZero() || o <= opts.Max.ordinal()) &&
(opts.DisableMonth == nil || !opts.DisableMonth(m))
},
muted: func(int, int) bool { return false },
})
to := *value
switch {
case cleared:
to = YearMonth{}
case ok:
to = yearMonthOf(picked)
}
changed := to != *value
if changed {
slog.Debug("mujica: month_picker change", "from", *value, "to", to)
*value = to
}
return DateTimeResult{Element: col, changed: changed}
}