mirror of
https://github.com/axzilla/templui.git
synced 2025-02-21 22:12:41 +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."),
|
||||
}) {
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
ShowcaseFile: showcase.Datepicker(),
|
||||
PreviewCodeFile: "datepicker.templ",
|
||||
SectionName: "Default",
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
@ -96,7 +96,6 @@ templ ThemePreview() {
|
||||
ID: "birthdate",
|
||||
Name: "birthdate",
|
||||
Placeholder: "Select your birth date",
|
||||
Format: "YYYY-MM-DD",
|
||||
})
|
||||
</div>
|
||||
<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
|
||||
|
||||
import "github.com/axzilla/goilerplate/pkg/components"
|
||||
import (
|
||||
"github.com/axzilla/goilerplate/pkg/components"
|
||||
"time"
|
||||
)
|
||||
|
||||
templ Datepicker() {
|
||||
templ DatepickerSelectedDate() {
|
||||
@components.Datepicker(components.DatepickerProps{
|
||||
ID: "my-datepicker",
|
||||
Name: "selected-date",
|
||||
Placeholder: "Select a date",
|
||||
Format: "YYYY-MM-DD",
|
||||
Value: time.Now(),
|
||||
Class: "w-full max-w-xs",
|
||||
})
|
||||
}
|
@ -3,247 +3,369 @@ package components
|
||||
import (
|
||||
"github.com/axzilla/goilerplate/pkg/icons"
|
||||
"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 {
|
||||
// ID uniquely identifies the datepicker input
|
||||
// ID sets the input element's ID
|
||||
ID string
|
||||
|
||||
// Name sets the form field name
|
||||
// Name sets the input element's name
|
||||
Name string
|
||||
|
||||
// Placeholder shows helper text when empty
|
||||
// Value sets the initial date
|
||||
Value time.Time
|
||||
// Config provides format and locale settings
|
||||
Config DatepickerConfig
|
||||
// Placeholder sets the input placeholder text
|
||||
Placeholder string
|
||||
|
||||
// Format controls date string presentation
|
||||
// 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
|
||||
|
||||
// Disabled controls whether the datepicker can be interacted with
|
||||
Disabled bool
|
||||
// Class adds custom CSS classes
|
||||
Class string
|
||||
|
||||
Value string
|
||||
|
||||
// Attributes for additional HTML attributes and Alpine.js bindings
|
||||
// Attributes allows additional HTML attributes
|
||||
Attributes templ.Attributes
|
||||
}
|
||||
|
||||
// Datepicker renders a calendar input component with popup date selection.
|
||||
// Uses Alpine.js for interactions and supports dark mode via Tailwind.
|
||||
// For detailed examples and usage guides, visit https://goilerplate.com/docs/components/datepicker
|
||||
// Datepicker renders a localized date picker component with configurable formatting.
|
||||
// Features:
|
||||
// - 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:
|
||||
// - ID: Unique identifier for the input
|
||||
// - ID: Input element identifier
|
||||
// - Name: Form field name
|
||||
// - Placeholder: Helper text when empty
|
||||
// - Format: Date display format
|
||||
// - Value: Initial date value
|
||||
// - Config: Format and locale settings
|
||||
// - Placeholder: Input placeholder text
|
||||
// - Disabled: Interactivity state
|
||||
// - Class: Additional CSS classes
|
||||
// - Attributes: Additional HTML attributes
|
||||
templ Datepicker(props DatepickerProps) {
|
||||
<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) }
|
||||
@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">
|
||||
<input
|
||||
x-ref="datePickerInput"
|
||||
type="text"
|
||||
id={ props.ID }
|
||||
name={ props.Name }
|
||||
placeholder={ props.Placeholder }
|
||||
:value="datePickerValue"
|
||||
x-modelable="datePickerValue"
|
||||
if props.Placeholder != "" {
|
||||
placeholder={ props.Placeholder }
|
||||
} else {
|
||||
placeholder="Select date"
|
||||
}
|
||||
disabled?={ props.Disabled }
|
||||
:value="value"
|
||||
x-modelable="value"
|
||||
@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
|
||||
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... }
|
||||
/>
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
@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{})
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
x-show="datePickerOpen"
|
||||
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-show="open"
|
||||
x-ref="datePickerPopup"
|
||||
: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"
|
||||
@click.away="open = false"
|
||||
x-transition.opacity
|
||||
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'}"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<span x-text="datePickerMonthNames[datePickerMonth]" class="text-lg font-bold"></span>
|
||||
<span x-text="datePickerYear" class="ml-1 text-lg font-normal text-muted-foreground"></span>
|
||||
</div>
|
||||
<div>
|
||||
<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">
|
||||
@icons.ChevronLeft(icons.IconProps{Size: "16", Class: "text-muted-foreground"})
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<span x-text="months[currentMonth] + ' ' + currentYear" class="text-sm font-medium"></span>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
@click="currentMonth--; if(currentMonth < 0) { currentMonth = 11; 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.ChevronLeft(icons.IconProps{})
|
||||
</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">
|
||||
@icons.ChevronRight(icons.IconProps{Size: "16", Class: "text-muted-foreground"})
|
||||
<button
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-7 mb-3">
|
||||
<template x-for="(day, index) in datePickerDays" :key="index">
|
||||
<div class="px-0.5">
|
||||
<div x-text="day" class="text-xs font-medium text-center text-muted-foreground"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-7 gap-1 mb-2">
|
||||
<template x-for="day in days" :key="day">
|
||||
<div class="text-center text-xs text-muted-foreground" x-text="day"></div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="grid grid-cols-7">
|
||||
<template x-for="blankDay in datePickerBlankDaysInMonth">
|
||||
<div class="p-1 text-sm text-center border border-transparent"></div>
|
||||
<div class="grid grid-cols-7 gap-1">
|
||||
<template x-for="blank in blankDays" :key="'blank' + blank">
|
||||
<div class="h-8 w-8"></div>
|
||||
</template>
|
||||
<template x-for="(day, dayIndex) in datePickerDaysInMonth" :key="dayIndex">
|
||||
<div class="px-0.5 mb-1 aspect-square">
|
||||
<div
|
||||
x-text="day"
|
||||
@click="datePickerDayClicked(day)"
|
||||
:class="{
|
||||
'bg-muted text-muted-foreground': datePickerIsToday(day) == true,
|
||||
'text-foreground hover:bg-accent hover:text-accent-foreground': datePickerIsToday(day) == false && datePickerIsSelectedDate(day) == false,
|
||||
'bg-primary text-primary-foreground hover:bg-primary/90': datePickerIsSelectedDate(day) == true
|
||||
}"
|
||||
class="flex items-center justify-center text-sm leading-none text-center rounded-full cursor-pointer h-7 w-7"
|
||||
></div>
|
||||
</div>
|
||||
<template x-for="day in monthDays" :key="day">
|
||||
<button
|
||||
type="button"
|
||||
@click="selectDate(day)"
|
||||
x-text="day"
|
||||
:class="{
|
||||
'bg-primary text-primary-foreground': isSelected(day),
|
||||
'text-red-500': isToday(day) && !isSelected(day),
|
||||
'hover:bg-accent hover:text-accent-foreground': !isSelected(day)
|
||||
}"
|
||||
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"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user