mirror of
https://github.com/axzilla/templui.git
synced 2025-02-22 02:53:09 +00:00
feat(datepicker): improve datepicker and add datepicker showcase examples with custom placeholders, formats, and disabled states
This commit is contained in:
parent
08eba291ff
commit
b884df0a2b
@ -13,8 +13,33 @@ templ Datepicker() {
|
|||||||
Description: templ.Raw("A date picker component."),
|
Description: templ.Raw("A date picker component."),
|
||||||
}) {
|
}) {
|
||||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||||
ShowcaseFile: showcase.Datepicker(),
|
SectionName: "Default",
|
||||||
PreviewCodeFile: "datepicker.templ",
|
ShowcaseFile: showcase.DatepickerDefault(),
|
||||||
|
PreviewCodeFile: "datepicker_default.templ",
|
||||||
|
ComponentCodeFile: "datepicker.templ",
|
||||||
|
})
|
||||||
|
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||||
|
SectionName: "Custom Placeholder",
|
||||||
|
ShowcaseFile: showcase.DatepickerCustomPlaceholder(),
|
||||||
|
PreviewCodeFile: "datepicker_custom_placeholder.templ",
|
||||||
|
ComponentCodeFile: "datepicker.templ",
|
||||||
|
})
|
||||||
|
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||||
|
SectionName: "Selected Date",
|
||||||
|
ShowcaseFile: showcase.DatepickerSelectedDate(),
|
||||||
|
PreviewCodeFile: "datepicker_selected_date.templ",
|
||||||
|
ComponentCodeFile: "datepicker.templ",
|
||||||
|
})
|
||||||
|
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||||
|
SectionName: "Disabled",
|
||||||
|
ShowcaseFile: showcase.DatepickerDisabled(),
|
||||||
|
PreviewCodeFile: "datepicker_disabled.templ",
|
||||||
|
ComponentCodeFile: "datepicker.templ",
|
||||||
|
})
|
||||||
|
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||||
|
SectionName: "Formats",
|
||||||
|
ShowcaseFile: showcase.DatepickerFormats(),
|
||||||
|
PreviewCodeFile: "datepicker_formats.templ",
|
||||||
ComponentCodeFile: "datepicker.templ",
|
ComponentCodeFile: "datepicker.templ",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,6 @@ templ ThemePreview() {
|
|||||||
ID: "birthdate",
|
ID: "birthdate",
|
||||||
Name: "birthdate",
|
Name: "birthdate",
|
||||||
Placeholder: "Select your birth date",
|
Placeholder: "Select your birth date",
|
||||||
Format: "YYYY-MM-DD",
|
|
||||||
})
|
})
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
12
internals/ui/showcase/datepicker_custom_placeholder.templ
Normal file
12
internals/ui/showcase/datepicker_custom_placeholder.templ
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package showcase
|
||||||
|
|
||||||
|
import "github.com/axzilla/goilerplate/pkg/components"
|
||||||
|
|
||||||
|
templ DatepickerCustomPlaceholder() {
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date",
|
||||||
|
Name: "date",
|
||||||
|
Placeholder: "When is your birthday?",
|
||||||
|
Class: "w-full max-w-xs",
|
||||||
|
})
|
||||||
|
}
|
11
internals/ui/showcase/datepicker_default.templ
Normal file
11
internals/ui/showcase/datepicker_default.templ
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package showcase
|
||||||
|
|
||||||
|
import "github.com/axzilla/goilerplate/pkg/components"
|
||||||
|
|
||||||
|
templ DatepickerDefault() {
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "my-datepicker",
|
||||||
|
Name: "selected-date",
|
||||||
|
Class: "w-full max-w-xs",
|
||||||
|
})
|
||||||
|
}
|
29
internals/ui/showcase/datepicker_disabled.templ
Normal file
29
internals/ui/showcase/datepicker_disabled.templ
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package showcase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/axzilla/goilerplate/pkg/components"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ DatepickerDisabled() {
|
||||||
|
<div class="w-full max-w-xs flex flex-col gap-4">
|
||||||
|
// Disabled using attributes
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "my-datepicker",
|
||||||
|
Name: "selected-date",
|
||||||
|
Placeholder: "Select a date",
|
||||||
|
Value: time.Now(),
|
||||||
|
Class: "w-full max-w-xs",
|
||||||
|
Attributes: templ.Attributes{"disabled": true},
|
||||||
|
})
|
||||||
|
// Disabled using props
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "my-datepicker-2",
|
||||||
|
Name: "selected-date-2",
|
||||||
|
Placeholder: "Select a date",
|
||||||
|
Value: time.Now(),
|
||||||
|
Class: "w-full max-w-xs",
|
||||||
|
Disabled: true,
|
||||||
|
})
|
||||||
|
</div>
|
||||||
|
}
|
76
internals/ui/showcase/datepicker_formats.templ
Normal file
76
internals/ui/showcase/datepicker_formats.templ
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package showcase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/axzilla/goilerplate/pkg/components"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ DatepickerFormats() {
|
||||||
|
<div class="w-full max-w-xs flex flex-col gap-4">
|
||||||
|
// Default ISO
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date-iso",
|
||||||
|
Name: "date_iso",
|
||||||
|
Config: components.DatePickerISO,
|
||||||
|
Value: time.Now(),
|
||||||
|
})
|
||||||
|
// Default EU
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date-eu",
|
||||||
|
Name: "date_eu",
|
||||||
|
Config: components.DatePickerEU,
|
||||||
|
Value: time.Now(),
|
||||||
|
})
|
||||||
|
// Default UK
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date-uk",
|
||||||
|
Name: "date_uk",
|
||||||
|
Config: components.DatePickerUK,
|
||||||
|
Value: time.Now(),
|
||||||
|
})
|
||||||
|
// Default US
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date-us",
|
||||||
|
Name: "date_us",
|
||||||
|
Config: components.DatePickerUS,
|
||||||
|
Value: time.Now(),
|
||||||
|
})
|
||||||
|
// Default LONG
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date-long",
|
||||||
|
Name: "date_long",
|
||||||
|
Config: components.DatePickerLONG,
|
||||||
|
Value: time.Now(),
|
||||||
|
})
|
||||||
|
// Custom Config
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date-es",
|
||||||
|
Name: "date_es",
|
||||||
|
Config: components.NewDatepickerConfig(
|
||||||
|
components.DateFormatLONG,
|
||||||
|
components.DateLocaleSpanish,
|
||||||
|
),
|
||||||
|
Value: time.Now().AddDate(0, 0, -30), // 30 days ago
|
||||||
|
Placeholder: "Seleccionar fecha",
|
||||||
|
})
|
||||||
|
// Custom Format (Japanese LONG)
|
||||||
|
@components.Datepicker(components.DatepickerProps{
|
||||||
|
ID: "date-custom",
|
||||||
|
Name: "date_custom",
|
||||||
|
Config: components.NewDatepickerConfig(
|
||||||
|
components.DateFormatLONG,
|
||||||
|
components.DateLocale{
|
||||||
|
MonthNames: []string{
|
||||||
|
"1月", "2月", "3月", "4月", "5月", "6月",
|
||||||
|
"7月", "8月", "9月", "10月", "11月", "12月",
|
||||||
|
},
|
||||||
|
DayNames: []string{
|
||||||
|
"日", "月", "火", "水", "木", "金", "土",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Value: time.Now().AddDate(0, 0, -30), // 30 days ago
|
||||||
|
Placeholder: "Seleccionar fecha",
|
||||||
|
})
|
||||||
|
</div>
|
||||||
|
}
|
@ -1,13 +1,16 @@
|
|||||||
package showcase
|
package showcase
|
||||||
|
|
||||||
import "github.com/axzilla/goilerplate/pkg/components"
|
import (
|
||||||
|
"github.com/axzilla/goilerplate/pkg/components"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
templ Datepicker() {
|
templ DatepickerSelectedDate() {
|
||||||
@components.Datepicker(components.DatepickerProps{
|
@components.Datepicker(components.DatepickerProps{
|
||||||
ID: "my-datepicker",
|
ID: "my-datepicker",
|
||||||
Name: "selected-date",
|
Name: "selected-date",
|
||||||
Placeholder: "Select a date",
|
Placeholder: "Select a date",
|
||||||
Format: "YYYY-MM-DD",
|
Value: time.Now(),
|
||||||
Class: "w-full max-w-xs",
|
Class: "w-full max-w-xs",
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -3,247 +3,369 @@ package components
|
|||||||
import (
|
import (
|
||||||
"github.com/axzilla/goilerplate/pkg/icons"
|
"github.com/axzilla/goilerplate/pkg/icons"
|
||||||
"github.com/axzilla/goilerplate/pkg/utils"
|
"github.com/axzilla/goilerplate/pkg/utils"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DateFormat defines the available date formatting styles
|
||||||
|
type DateFormat string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DateFormatISO represents ISO format (YYYY-MM-DD)
|
||||||
|
DateFormatISO DateFormat = "iso"
|
||||||
|
// DateFormatEU represents European format (DD.MM.YYYY)
|
||||||
|
DateFormatEU DateFormat = "eu"
|
||||||
|
// DateFormatUK represents UK format (DD/MM/YYYY)
|
||||||
|
DateFormatUK DateFormat = "uk"
|
||||||
|
// DateFormatUS represents US format (MM/DD/YYYY)
|
||||||
|
DateFormatUS DateFormat = "us"
|
||||||
|
// DateFormatLONG represents long format (Month DD, YYYY)
|
||||||
|
DateFormatLONG DateFormat = "long"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dateFormatMapping maps DateFormat to Go time format strings
|
||||||
|
var dateFormatMapping = map[DateFormat]string{
|
||||||
|
DateFormatISO: "2006-01-02",
|
||||||
|
DateFormatEU: "02.01.2006",
|
||||||
|
DateFormatUK: "02/01/2006",
|
||||||
|
DateFormatUS: "01/02/2006",
|
||||||
|
DateFormatLONG: "January 2, 2006",
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateLocale defines localization settings for the datepicker
|
||||||
|
type DateLocale struct {
|
||||||
|
// MonthNames contains the localized names of months
|
||||||
|
MonthNames []string
|
||||||
|
// DayNames contains the localized abbreviated day names
|
||||||
|
DayNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateLocaleDefault provides English localization
|
||||||
|
var DateLocaleDefault = DateLocale{
|
||||||
|
MonthNames: []string{"January", "February", "March", "April", "May", "June",
|
||||||
|
"July", "August", "September", "October", "November", "December"},
|
||||||
|
DayNames: []string{"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-defined locales for different languages
|
||||||
|
var (
|
||||||
|
// DateLocaleSpanish provides Spanish localization
|
||||||
|
DateLocaleSpanish = DateLocale{
|
||||||
|
MonthNames: []string{"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
|
||||||
|
"Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"},
|
||||||
|
DayNames: []string{"Lu", "Ma", "Mi", "Ju", "Vi", "Sa", "Do"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateLocaleGerman provides German localization
|
||||||
|
DateLocaleGerman = DateLocale{
|
||||||
|
MonthNames: []string{"Januar", "Februar", "März", "April", "Mai", "Juni",
|
||||||
|
"Juli", "August", "September", "Oktober", "November", "Dezember"},
|
||||||
|
DayNames: []string{"Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateLocaleFrench provides French localization
|
||||||
|
DateLocaleFrench = DateLocale{
|
||||||
|
MonthNames: []string{"Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
|
||||||
|
"Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"},
|
||||||
|
DayNames: []string{"Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateLocaleItalian provides Italian localization
|
||||||
|
DateLocaleItalian = DateLocale{
|
||||||
|
MonthNames: []string{"Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno",
|
||||||
|
"Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"},
|
||||||
|
DayNames: []string{"Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// DatepickerConfig combines format and locale settings
|
||||||
|
type DatepickerConfig struct {
|
||||||
|
Format DateFormat
|
||||||
|
Locale DateLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-defined datepicker configurations
|
||||||
|
var (
|
||||||
|
// DatePickerISO provides ISO format with default locale
|
||||||
|
DatePickerISO = DatepickerConfig{
|
||||||
|
Format: DateFormatISO,
|
||||||
|
Locale: DateLocaleDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatePickerEU provides European format with default locale
|
||||||
|
DatePickerEU = DatepickerConfig{
|
||||||
|
Format: DateFormatEU,
|
||||||
|
Locale: DateLocaleDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatePickerUK provides UK format with default locale
|
||||||
|
DatePickerUK = DatepickerConfig{
|
||||||
|
Format: DateFormatUK,
|
||||||
|
Locale: DateLocaleDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatePickerUS provides US format with default locale
|
||||||
|
DatePickerUS = DatepickerConfig{
|
||||||
|
Format: DateFormatUS,
|
||||||
|
Locale: DateLocaleDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatePickerUS provides US format with default locale
|
||||||
|
DatePickerLONG = DatepickerConfig{
|
||||||
|
Format: DateFormatLONG,
|
||||||
|
Locale: DateLocaleDefault,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDatepickerConfig creates a new configuration with specified format and locale
|
||||||
|
func NewDatepickerConfig(format DateFormat, locale DateLocale) DatepickerConfig {
|
||||||
|
return DatepickerConfig{
|
||||||
|
Format: format,
|
||||||
|
Locale: locale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGoFormat returns the Go time format string for the current configuration
|
||||||
|
func (c DatepickerConfig) GetGoFormat() string {
|
||||||
|
if format, ok := dateFormatMapping[c.Format]; ok {
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
return dateFormatMapping[DateFormatISO] // Default to ISO
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatepickerProps defines all available props for the Datepicker component
|
||||||
type DatepickerProps struct {
|
type DatepickerProps struct {
|
||||||
// ID uniquely identifies the datepicker input
|
// ID sets the input element's ID
|
||||||
ID string
|
ID string
|
||||||
|
// Name sets the input element's name
|
||||||
// Name sets the form field name
|
|
||||||
Name string
|
Name string
|
||||||
|
// Value sets the initial date
|
||||||
// Placeholder shows helper text when empty
|
Value time.Time
|
||||||
|
// Config provides format and locale settings
|
||||||
|
Config DatepickerConfig
|
||||||
|
// Placeholder sets the input placeholder text
|
||||||
Placeholder string
|
Placeholder string
|
||||||
|
// Disabled controls whether the datepicker can be interacted with
|
||||||
// Format controls date string presentation
|
Disabled bool
|
||||||
// Supported formats:
|
|
||||||
// - "M d, Y" (Jan 1, 2024)
|
|
||||||
// - "MM-DD-YYYY" (01-01-2024)
|
|
||||||
// - "DD-MM-YYYY" (01-01-2024)
|
|
||||||
// - "YYYY-MM-DD" (2024-01-01)
|
|
||||||
// - "D d M, Y" (Mon 1 Jan, 2024)
|
|
||||||
Format string
|
|
||||||
|
|
||||||
// Class adds custom CSS classes
|
// Class adds custom CSS classes
|
||||||
Class string
|
Class string
|
||||||
|
// Attributes allows additional HTML attributes
|
||||||
Value string
|
|
||||||
|
|
||||||
// Attributes for additional HTML attributes and Alpine.js bindings
|
|
||||||
Attributes templ.Attributes
|
Attributes templ.Attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Datepicker renders a calendar input component with popup date selection.
|
// Datepicker renders a localized date picker component with configurable formatting.
|
||||||
// Uses Alpine.js for interactions and supports dark mode via Tailwind.
|
// Features:
|
||||||
// For detailed examples and usage guides, visit https://goilerplate.com/docs/components/datepicker
|
// - Multiple date formats (ISO, EU, US, UK, Long)
|
||||||
|
// - Localization support for months and weekdays
|
||||||
|
// - Popup calendar for date selection
|
||||||
|
// - Keyboard navigation support
|
||||||
|
// - Responsive positioning
|
||||||
|
// - Custom styling support
|
||||||
//
|
//
|
||||||
// Props:
|
// Props:
|
||||||
// - ID: Unique identifier for the input
|
// - ID: Input element identifier
|
||||||
// - Name: Form field name
|
// - Name: Form field name
|
||||||
// - Placeholder: Helper text when empty
|
// - Value: Initial date value
|
||||||
// - Format: Date display format
|
// - Config: Format and locale settings
|
||||||
|
// - Placeholder: Input placeholder text
|
||||||
|
// - Disabled: Interactivity state
|
||||||
// - Class: Additional CSS classes
|
// - Class: Additional CSS classes
|
||||||
// - Attributes: Additional HTML attributes
|
// - Attributes: Additional HTML attributes
|
||||||
templ Datepicker(props DatepickerProps) {
|
templ Datepicker(props DatepickerProps) {
|
||||||
<div
|
<div
|
||||||
data-date-format={ props.Format }
|
|
||||||
data-date-value={ props.Value }
|
|
||||||
x-data="{
|
|
||||||
datePickerOpen: false,
|
|
||||||
datePickerValue: $el.getAttribute('data-date-value'),
|
|
||||||
datePickerFormat: $el.getAttribute('data-date-format'),
|
|
||||||
datePickerMonth: '',
|
|
||||||
datePickerYear: '',
|
|
||||||
datePickerDay: '',
|
|
||||||
datePickerDaysInMonth: [],
|
|
||||||
datePickerBlankDaysInMonth: [],
|
|
||||||
datePickerMonthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
|
||||||
datePickerDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
||||||
position: 'bottom',
|
|
||||||
toggleDatePicker() {
|
|
||||||
this.datePickerOpen = !this.datePickerOpen;
|
|
||||||
if (this.datePickerOpen) {
|
|
||||||
this.$nextTick(() => this.updatePosition());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updatePosition() {
|
|
||||||
const trigger = this.$refs.datePickerInput;
|
|
||||||
const popup = this.$refs.datePickerPopup;
|
|
||||||
const rect = trigger.getBoundingClientRect();
|
|
||||||
const popupRect = popup.getBoundingClientRect();
|
|
||||||
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
||||||
|
|
||||||
if (rect.bottom + popupRect.height > viewportHeight && rect.top > popupRect.height) {
|
|
||||||
this.position = 'top';
|
|
||||||
} else {
|
|
||||||
this.position = 'bottom';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
datePickerDayClicked(day) {
|
|
||||||
let selectedDate = new Date(this.datePickerYear, this.datePickerMonth, day);
|
|
||||||
this.datePickerDay = day;
|
|
||||||
this.datePickerValue = this.datePickerFormatDate(selectedDate);
|
|
||||||
this.datePickerIsSelectedDate(day);
|
|
||||||
this.datePickerOpen = false;
|
|
||||||
},
|
|
||||||
datePickerPreviousMonth(){
|
|
||||||
if (this.datePickerMonth == 0) {
|
|
||||||
this.datePickerYear--;
|
|
||||||
this.datePickerMonth = 11;
|
|
||||||
} else {
|
|
||||||
this.datePickerMonth--;
|
|
||||||
}
|
|
||||||
this.datePickerCalculateDays();
|
|
||||||
},
|
|
||||||
datePickerNextMonth(){
|
|
||||||
if (this.datePickerMonth == 11) {
|
|
||||||
this.datePickerMonth = 0;
|
|
||||||
this.datePickerYear++;
|
|
||||||
} else {
|
|
||||||
this.datePickerMonth++;
|
|
||||||
}
|
|
||||||
this.datePickerCalculateDays();
|
|
||||||
},
|
|
||||||
datePickerIsSelectedDate(day) {
|
|
||||||
const d = new Date(this.datePickerYear, this.datePickerMonth, day);
|
|
||||||
return this.datePickerValue === this.datePickerFormatDate(d) ? true : false;
|
|
||||||
},
|
|
||||||
datePickerIsToday(day) {
|
|
||||||
const today = new Date();
|
|
||||||
const d = new Date(this.datePickerYear, this.datePickerMonth, day);
|
|
||||||
return today.toDateString() === d.toDateString() ? true : false;
|
|
||||||
},
|
|
||||||
datePickerCalculateDays() {
|
|
||||||
let daysInMonth = new Date(this.datePickerYear, this.datePickerMonth + 1, 0).getDate();
|
|
||||||
let dayOfWeek = new Date(this.datePickerYear, this.datePickerMonth).getDay();
|
|
||||||
let blankdaysArray = [];
|
|
||||||
for (var i = 1; i <= dayOfWeek; i++) {
|
|
||||||
blankdaysArray.push(i);
|
|
||||||
}
|
|
||||||
let daysArray = [];
|
|
||||||
for (var i = 1; i <= daysInMonth; i++) {
|
|
||||||
daysArray.push(i);
|
|
||||||
}
|
|
||||||
this.datePickerBlankDaysInMonth = blankdaysArray;
|
|
||||||
this.datePickerDaysInMonth = daysArray;
|
|
||||||
},
|
|
||||||
datePickerFormatDate(date) {
|
|
||||||
let formattedDay = this.datePickerDays[date.getDay()];
|
|
||||||
let formattedDate = ('0' + date.getDate()).slice(-2);
|
|
||||||
let formattedMonth = this.datePickerMonthNames[date.getMonth()];
|
|
||||||
let formattedMonthShortName = this.datePickerMonthNames[date.getMonth()].substring(0, 3);
|
|
||||||
let formattedMonthInNumber = ('0' + (parseInt(date.getMonth()) + 1)).slice(-2);
|
|
||||||
let formattedYear = date.getFullYear();
|
|
||||||
|
|
||||||
if (this.datePickerFormat === 'M d, Y') {
|
|
||||||
return `${formattedMonthShortName} ${formattedDate}, ${formattedYear}`;
|
|
||||||
}
|
|
||||||
if (this.datePickerFormat === 'MM-DD-YYYY') {
|
|
||||||
return `${formattedMonthInNumber}-${formattedDate}-${formattedYear}`;
|
|
||||||
}
|
|
||||||
if (this.datePickerFormat === 'DD-MM-YYYY') {
|
|
||||||
return `${formattedDate}-${formattedMonthInNumber}-${formattedYear}`;
|
|
||||||
}
|
|
||||||
if (this.datePickerFormat === 'YYYY-MM-DD') {
|
|
||||||
return `${formattedYear}-${formattedMonthInNumber}-${formattedDate}`;
|
|
||||||
}
|
|
||||||
if (this.datePickerFormat === 'D d M, Y') {
|
|
||||||
return `${formattedDay} ${formattedDate} ${formattedMonthShortName} ${formattedYear}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${formattedMonth} ${formattedDate}, ${formattedYear}`;
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
x-init="
|
|
||||||
currentDate = new Date();
|
|
||||||
if (datePickerValue) {
|
|
||||||
currentDate = new Date(Date.parse(datePickerValue));
|
|
||||||
}
|
|
||||||
datePickerMonth = currentDate.getMonth();
|
|
||||||
datePickerYear = currentDate.getFullYear();
|
|
||||||
datePickerDay = currentDate.getDate();
|
|
||||||
datePickerValue = datePickerFormatDate(currentDate);
|
|
||||||
datePickerCalculateDays();
|
|
||||||
"
|
|
||||||
class={ utils.TwMerge("relative", props.Class) }
|
class={ utils.TwMerge("relative", props.Class) }
|
||||||
@resize.window="if (datePickerOpen) updatePosition()"
|
if props.Value != (time.Time{}) {
|
||||||
|
data-value={ props.Value.Format(props.Config.GetGoFormat()) }
|
||||||
|
}
|
||||||
|
data-format={ string(props.Config.Format) }
|
||||||
|
data-monthnames={ templ.JSONString(props.Config.Locale.MonthNames) }
|
||||||
|
data-daynames={ templ.JSONString(props.Config.Locale.DayNames) }
|
||||||
|
x-data="{
|
||||||
|
open: false,
|
||||||
|
value: null,
|
||||||
|
format: $el.dataset.format,
|
||||||
|
currentMonth: 5,
|
||||||
|
currentYear: new Date().getFullYear(),
|
||||||
|
monthDays: [],
|
||||||
|
blankDays: [],
|
||||||
|
months: JSON.parse($el.dataset.monthnames) || ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||||
|
days: JSON.parse($el.dataset.daynames) || ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
|
||||||
|
|
||||||
|
position: 'bottom',
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const initialDate = $el.dataset.value ? new Date(this.parseDate($el.dataset.value)) : new Date();
|
||||||
|
this.currentMonth = initialDate.getMonth();
|
||||||
|
this.currentYear = initialDate.getFullYear();
|
||||||
|
this.calculateDays();
|
||||||
|
// Format the initial value using the correct locale
|
||||||
|
if ($el.dataset.value) {
|
||||||
|
this.value = this.formatDate(initialDate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleDatePicker() {
|
||||||
|
this.open = !this.open;
|
||||||
|
if (this.open) {
|
||||||
|
this.$nextTick(() => this.updatePosition());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePosition() {
|
||||||
|
const trigger = this.$refs.datePickerInput;
|
||||||
|
const popup = this.$refs.datePickerPopup;
|
||||||
|
const rect = trigger.getBoundingClientRect();
|
||||||
|
const popupRect = popup.getBoundingClientRect();
|
||||||
|
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
|
||||||
|
if (rect.bottom + popupRect.height > viewportHeight && rect.top > popupRect.height) {
|
||||||
|
this.position = 'top';
|
||||||
|
} else {
|
||||||
|
this.position = 'bottom';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateDays() {
|
||||||
|
const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();
|
||||||
|
const daysInMonth = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
|
||||||
|
|
||||||
|
this.blankDays = Array.from({ length: firstDay }, (_, i) => i);
|
||||||
|
this.monthDays = Array.from({ length: daysInMonth }, (_, i) => i + 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
parseDate(dateStr) {
|
||||||
|
const parts = dateStr.split(/[-/.]/);
|
||||||
|
switch(this.format) {
|
||||||
|
case 'eu':
|
||||||
|
return `${parts[2]}-${parts[1]}-${parts[0]}`;
|
||||||
|
case 'us':
|
||||||
|
return `${parts[2]}-${parts[0]}-${parts[1]}`;
|
||||||
|
case 'uk':
|
||||||
|
return `${parts[2]}-${parts[1]}-${parts[0]}`;
|
||||||
|
case 'long':
|
||||||
|
case 'iso':
|
||||||
|
default:
|
||||||
|
return dateStr; // Für ISO und long Format das Datum unverändert lassen
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
formatDate(date) {
|
||||||
|
const d = date.getDate().toString().padStart(2, '0');
|
||||||
|
const m = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const y = date.getFullYear();
|
||||||
|
|
||||||
|
switch(this.format) {
|
||||||
|
case 'eu':
|
||||||
|
return `${d}.${m}.${y}`;
|
||||||
|
case 'uk':
|
||||||
|
return `${d}/${m}/${y}`;
|
||||||
|
case 'us':
|
||||||
|
return `${m}/${d}/${y}`;
|
||||||
|
case 'long':
|
||||||
|
// Use the months array from the provided locale
|
||||||
|
return `${this.months[date.getMonth()]} ${d}, ${y}`;
|
||||||
|
// case 'iso'
|
||||||
|
default:
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isToday(day) {
|
||||||
|
const today = new Date();
|
||||||
|
const date = new Date(this.currentYear, this.currentMonth, day);
|
||||||
|
return date.toDateString() === today.toDateString();
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelected(day) {
|
||||||
|
if (!this.value) return false;
|
||||||
|
const date = new Date(this.currentYear, this.currentMonth, day);
|
||||||
|
const selected = new Date(this.parseDate(this.value));
|
||||||
|
return date.toDateString() === selected.toDateString();
|
||||||
|
},
|
||||||
|
|
||||||
|
selectDate(day) {
|
||||||
|
const date = new Date(this.currentYear, this.currentMonth, day);
|
||||||
|
this.value = this.formatDate(date);
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@resize.window="if (open) updatePosition()"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
|
x-ref="datePickerInput"
|
||||||
type="text"
|
type="text"
|
||||||
id={ props.ID }
|
id={ props.ID }
|
||||||
name={ props.Name }
|
name={ props.Name }
|
||||||
placeholder={ props.Placeholder }
|
if props.Placeholder != "" {
|
||||||
:value="datePickerValue"
|
placeholder={ props.Placeholder }
|
||||||
x-modelable="datePickerValue"
|
} else {
|
||||||
|
placeholder="Select date"
|
||||||
|
}
|
||||||
|
disabled?={ props.Disabled }
|
||||||
|
:value="value"
|
||||||
|
x-modelable="value"
|
||||||
@click="toggleDatePicker()"
|
@click="toggleDatePicker()"
|
||||||
x-on:keydown.escape="datePickerOpen = false"
|
|
||||||
x-ref="datePickerInput"
|
|
||||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
readonly
|
readonly
|
||||||
|
class="peer flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
{ props.Attributes... }
|
{ props.Attributes... }
|
||||||
/>
|
/>
|
||||||
<div
|
<button
|
||||||
|
type="button"
|
||||||
@click="toggleDatePicker()"
|
@click="toggleDatePicker()"
|
||||||
class="absolute top-0 right-0 px-3 py-2 cursor-pointer text-muted-foreground hover:text-foreground"
|
disabled?={ props.Disabled }
|
||||||
|
class="peer-disabled:pointer-events-none peer-disabled:opacity-50 absolute top-0 right-0 px-3 py-2 cursor-pointer text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
@icons.Calendar(icons.IconProps{})
|
@icons.Calendar(icons.IconProps{})
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
x-show="datePickerOpen"
|
x-show="open"
|
||||||
x-transition:enter="transition ease-out duration-100"
|
|
||||||
x-transition:enter-start="transform opacity-0 scale-95"
|
|
||||||
x-transition:enter-end="transform opacity-100 scale-100"
|
|
||||||
x-transition:leave="transition ease-in duration-75"
|
|
||||||
x-transition:leave-start="transform opacity-100 scale-100"
|
|
||||||
x-transition:leave-end="transform opacity-0 scale-95"
|
|
||||||
@click.away="datePickerOpen = false"
|
|
||||||
x-ref="datePickerPopup"
|
x-ref="datePickerPopup"
|
||||||
:class="{
|
@click.away="open = false"
|
||||||
'top-full mt-1': position === 'bottom',
|
x-transition.opacity
|
||||||
'bottom-full mb-1': position === 'top'
|
class="absolute left-0 z-50 mt-1 w-64 rounded-lg border bg-popover p-4 shadow-md"
|
||||||
}"
|
:class="{'top-full mt-1': position === 'bottom','bottom-full mb-1': position === 'top'}"
|
||||||
class="absolute left-0 z-50 w-64 p-4 mt-1 antialiased bg-popover text-popover-foreground border rounded-lg shadow border-border"
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div>
|
<span x-text="months[currentMonth] + ' ' + currentYear" class="text-sm font-medium"></span>
|
||||||
<span x-text="datePickerMonthNames[datePickerMonth]" class="text-lg font-bold"></span>
|
<div class="flex gap-1">
|
||||||
<span x-text="datePickerYear" class="ml-1 text-lg font-normal text-muted-foreground"></span>
|
<button
|
||||||
</div>
|
type="button"
|
||||||
<div>
|
@click="currentMonth--; if(currentMonth < 0) { currentMonth = 11; currentYear--; } calculateDays()"
|
||||||
<button @click="datePickerPreviousMonth()" type="button" class="inline-flex p-1 transition duration-100 ease-in-out rounded-full cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground">
|
class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-7 w-7"
|
||||||
@icons.ChevronLeft(icons.IconProps{Size: "16", Class: "text-muted-foreground"})
|
>
|
||||||
|
@icons.ChevronLeft(icons.IconProps{})
|
||||||
</button>
|
</button>
|
||||||
<button @click="datePickerNextMonth()" type="button" class="inline-flex p-1 transition duration-100 ease-in-out rounded-full cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground">
|
<button
|
||||||
@icons.ChevronRight(icons.IconProps{Size: "16", Class: "text-muted-foreground"})
|
type="button"
|
||||||
|
@click="currentMonth++; if(currentMonth > 11) { currentMonth = 0; currentYear++; } calculateDays()"
|
||||||
|
class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-7 w-7"
|
||||||
|
>
|
||||||
|
@icons.ChevronRight(icons.IconProps{})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-7 mb-3">
|
<div class="grid grid-cols-7 gap-1 mb-2">
|
||||||
<template x-for="(day, index) in datePickerDays" :key="index">
|
<template x-for="day in days" :key="day">
|
||||||
<div class="px-0.5">
|
<div class="text-center text-xs text-muted-foreground" x-text="day"></div>
|
||||||
<div x-text="day" class="text-xs font-medium text-center text-muted-foreground"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-7">
|
<div class="grid grid-cols-7 gap-1">
|
||||||
<template x-for="blankDay in datePickerBlankDaysInMonth">
|
<template x-for="blank in blankDays" :key="'blank' + blank">
|
||||||
<div class="p-1 text-sm text-center border border-transparent"></div>
|
<div class="h-8 w-8"></div>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="(day, dayIndex) in datePickerDaysInMonth" :key="dayIndex">
|
<template x-for="day in monthDays" :key="day">
|
||||||
<div class="px-0.5 mb-1 aspect-square">
|
<button
|
||||||
<div
|
type="button"
|
||||||
x-text="day"
|
@click="selectDate(day)"
|
||||||
@click="datePickerDayClicked(day)"
|
x-text="day"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-muted text-muted-foreground': datePickerIsToday(day) == true,
|
'bg-primary text-primary-foreground': isSelected(day),
|
||||||
'text-foreground hover:bg-accent hover:text-accent-foreground': datePickerIsToday(day) == false && datePickerIsSelectedDate(day) == false,
|
'text-red-500': isToday(day) && !isSelected(day),
|
||||||
'bg-primary text-primary-foreground hover:bg-primary/90': datePickerIsSelectedDate(day) == true
|
'hover:bg-accent hover:text-accent-foreground': !isSelected(day)
|
||||||
}"
|
}"
|
||||||
class="flex items-center justify-center text-sm leading-none text-center rounded-full cursor-pointer h-7 w-7"
|
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||||
></div>
|
></button>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user