1
0
mirror of https://github.com/axzilla/templui.git synced 2025-02-21 01:32:45 +00:00

feat(datepicker): improve datepicker and add datepicker showcase examples with custom placeholders, formats, and disabled states

This commit is contained in:
axzilla 2024-11-30 11:06:22 +07:00
parent 08eba291ff
commit b884df0a2b
9 changed files with 736 additions and 266 deletions

View File

@ -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",
})
}

View File

@ -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">

View 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",
})
}

View 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",
})
}

View 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>
}

View 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>
}

View File

@ -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",
})
}

View File

@ -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