Add new macro files for comprehensive UI coverage: - modals.html: modal, confirm_modal, form_modal, slide_over - dropdowns.html: dropdown, context_menu, dropdown_item, select_dropdown - avatars.html: avatar, avatar_with_status, avatar_initials, avatar_group, user_avatar_card - charts.html: chart_card, line_chart, bar_chart, doughnut_chart (Chart.js) - datepicker.html: datepicker, daterange_picker, datetime_picker, time_picker (Flatpickr) Update forms.html with: - password_input: Password field with show/hide toggle and strength indicator - input_with_icon: Input with left/right icon support - file_input: Drag & drop file upload zone Tech stack: Jinja2 + Alpine.js + Tailwind CSS External libs: Chart.js (optional), Flatpickr (optional) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
356 lines
15 KiB
HTML
356 lines
15 KiB
HTML
{#
|
|
Datepicker Macros
|
|
=================
|
|
Date and time picker components using Flatpickr with Alpine.js integration.
|
|
|
|
Prerequisites:
|
|
Add Flatpickr CDN to your base template:
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/dark.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
|
|
|
Usage:
|
|
{% from 'shared/macros/datepicker.html' import datepicker, daterange_picker, datetime_picker %}
|
|
|
|
{# Basic date picker #}
|
|
{{ datepicker('startDate', 'formData.startDate', label='Start Date') }}
|
|
|
|
{# Date range picker #}
|
|
{{ daterange_picker('dateRange', 'formData.dateRange', label='Date Range') }}
|
|
|
|
{# Date and time picker #}
|
|
{{ datetime_picker('scheduledAt', 'formData.scheduledAt', label='Schedule') }}
|
|
#}
|
|
|
|
|
|
{#
|
|
Datepicker
|
|
==========
|
|
A single date picker input.
|
|
|
|
Parameters:
|
|
- id: Unique ID for the input
|
|
- x_model: Alpine.js x-model binding
|
|
- label: Input label (optional)
|
|
- placeholder: Placeholder text (default: 'Select date')
|
|
- format: Date format (default: 'Y-m-d')
|
|
- min_date: Minimum selectable date (default: none)
|
|
- max_date: Maximum selectable date (default: none)
|
|
- disabled: Alpine.js expression for disabled state
|
|
- required: Whether the field is required
|
|
- error: Alpine.js expression for error message
|
|
- class_extra: Additional CSS classes
|
|
#}
|
|
{% macro datepicker(id, x_model, label=none, placeholder='Select date', format='Y-m-d', min_date=none, max_date=none, disabled=none, required=false, error=none, class_extra='') %}
|
|
<div class="mb-4 {{ class_extra }}">
|
|
{% if label %}
|
|
<label for="{{ id }}" class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1.5">
|
|
{{ label }}{% if required %} <span class="text-red-600">*</span>{% endif %}
|
|
</label>
|
|
{% endif %}
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
id="{{ id }}"
|
|
x-model="{{ x_model }}"
|
|
x-init="flatpickr($el, {
|
|
dateFormat: '{{ format }}',
|
|
{% if min_date %}minDate: '{{ min_date }}',{% endif %}
|
|
{% if max_date %}maxDate: '{{ max_date }}',{% endif %}
|
|
disableMobile: true,
|
|
onChange: function(selectedDates, dateStr) {
|
|
{{ x_model }} = dateStr;
|
|
}
|
|
})"
|
|
placeholder="{{ placeholder }}"
|
|
{% if required %}required{% endif %}
|
|
{% if disabled %}:disabled="{{ disabled }}"{% endif %}
|
|
class="block w-full pl-10 pr-4 py-2.5 text-sm text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 focus:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
{% if error %}:class="{ 'border-red-500 focus:border-red-500 focus:ring-red-500/20': {{ error }} }"{% endif %}
|
|
readonly
|
|
>
|
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{% if error %}
|
|
<p x-show="{{ error }}" class="mt-1 text-xs text-red-600 dark:text-red-400" x-text="{{ error }}"></p>
|
|
{% endif %}
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Date Range Picker
|
|
=================
|
|
A date range picker for selecting start and end dates.
|
|
|
|
Parameters:
|
|
- id: Unique ID for the input
|
|
- x_model: Alpine.js x-model binding (will contain "YYYY-MM-DD to YYYY-MM-DD")
|
|
- label: Input label (optional)
|
|
- placeholder: Placeholder text (default: 'Select date range')
|
|
- format: Date format (default: 'Y-m-d')
|
|
- min_date: Minimum selectable date
|
|
- max_date: Maximum selectable date
|
|
- disabled: Alpine.js expression for disabled state
|
|
- required: Whether the field is required
|
|
#}
|
|
{% macro daterange_picker(id, x_model, label=none, placeholder='Select date range', format='Y-m-d', min_date=none, max_date=none, disabled=none, required=false) %}
|
|
<div class="mb-4">
|
|
{% if label %}
|
|
<label for="{{ id }}" class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1.5">
|
|
{{ label }}{% if required %} <span class="text-red-600">*</span>{% endif %}
|
|
</label>
|
|
{% endif %}
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
id="{{ id }}"
|
|
x-model="{{ x_model }}"
|
|
x-init="flatpickr($el, {
|
|
mode: 'range',
|
|
dateFormat: '{{ format }}',
|
|
{% if min_date %}minDate: '{{ min_date }}',{% endif %}
|
|
{% if max_date %}maxDate: '{{ max_date }}',{% endif %}
|
|
disableMobile: true,
|
|
onChange: function(selectedDates, dateStr) {
|
|
{{ x_model }} = dateStr;
|
|
}
|
|
})"
|
|
placeholder="{{ placeholder }}"
|
|
{% if required %}required{% endif %}
|
|
{% if disabled %}:disabled="{{ disabled }}"{% endif %}
|
|
class="block w-full pl-10 pr-4 py-2.5 text-sm text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 focus:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
readonly
|
|
>
|
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
DateTime Picker
|
|
===============
|
|
A date and time picker.
|
|
|
|
Parameters:
|
|
- id: Unique ID for the input
|
|
- x_model: Alpine.js x-model binding
|
|
- label: Input label (optional)
|
|
- placeholder: Placeholder text (default: 'Select date and time')
|
|
- format: DateTime format (default: 'Y-m-d H:i')
|
|
- enable_time: Whether to enable time selection (default: true)
|
|
- time_24hr: Use 24-hour time format (default: true)
|
|
- minute_increment: Minute increment for time picker (default: 5)
|
|
- min_date: Minimum selectable date
|
|
- max_date: Maximum selectable date
|
|
- disabled: Alpine.js expression for disabled state
|
|
- required: Whether the field is required
|
|
#}
|
|
{% macro datetime_picker(id, x_model, label=none, placeholder='Select date and time', format='Y-m-d H:i', enable_time=true, time_24hr=true, minute_increment=5, min_date=none, max_date=none, disabled=none, required=false) %}
|
|
<div class="mb-4">
|
|
{% if label %}
|
|
<label for="{{ id }}" class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1.5">
|
|
{{ label }}{% if required %} <span class="text-red-600">*</span>{% endif %}
|
|
</label>
|
|
{% endif %}
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
id="{{ id }}"
|
|
x-model="{{ x_model }}"
|
|
x-init="flatpickr($el, {
|
|
enableTime: {{ 'true' if enable_time else 'false' }},
|
|
time_24hr: {{ 'true' if time_24hr else 'false' }},
|
|
minuteIncrement: {{ minute_increment }},
|
|
dateFormat: '{{ format }}',
|
|
{% if min_date %}minDate: '{{ min_date }}',{% endif %}
|
|
{% if max_date %}maxDate: '{{ max_date }}',{% endif %}
|
|
disableMobile: true,
|
|
onChange: function(selectedDates, dateStr) {
|
|
{{ x_model }} = dateStr;
|
|
}
|
|
})"
|
|
placeholder="{{ placeholder }}"
|
|
{% if required %}required{% endif %}
|
|
{% if disabled %}:disabled="{{ disabled }}"{% endif %}
|
|
class="block w-full pl-10 pr-4 py-2.5 text-sm text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 focus:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
readonly
|
|
>
|
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Time Picker
|
|
===========
|
|
A time-only picker.
|
|
|
|
Parameters:
|
|
- id: Unique ID for the input
|
|
- x_model: Alpine.js x-model binding
|
|
- label: Input label (optional)
|
|
- placeholder: Placeholder text (default: 'Select time')
|
|
- format: Time format (default: 'H:i')
|
|
- time_24hr: Use 24-hour time format (default: true)
|
|
- minute_increment: Minute increment (default: 5)
|
|
- disabled: Alpine.js expression for disabled state
|
|
- required: Whether the field is required
|
|
#}
|
|
{% macro time_picker(id, x_model, label=none, placeholder='Select time', format='H:i', time_24hr=true, minute_increment=5, disabled=none, required=false) %}
|
|
<div class="mb-4">
|
|
{% if label %}
|
|
<label for="{{ id }}" class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1.5">
|
|
{{ label }}{% if required %} <span class="text-red-600">*</span>{% endif %}
|
|
</label>
|
|
{% endif %}
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
id="{{ id }}"
|
|
x-model="{{ x_model }}"
|
|
x-init="flatpickr($el, {
|
|
enableTime: true,
|
|
noCalendar: true,
|
|
time_24hr: {{ 'true' if time_24hr else 'false' }},
|
|
minuteIncrement: {{ minute_increment }},
|
|
dateFormat: '{{ format }}',
|
|
disableMobile: true,
|
|
onChange: function(selectedDates, dateStr) {
|
|
{{ x_model }} = dateStr;
|
|
}
|
|
})"
|
|
placeholder="{{ placeholder }}"
|
|
{% if required %}required{% endif %}
|
|
{% if disabled %}:disabled="{{ disabled }}"{% endif %}
|
|
class="block w-full pl-10 pr-4 py-2.5 text-sm text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 focus:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
readonly
|
|
>
|
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Month Picker
|
|
============
|
|
A month-only picker for selecting year and month.
|
|
|
|
Parameters:
|
|
- id: Unique ID for the input
|
|
- x_model: Alpine.js x-model binding
|
|
- label: Input label (optional)
|
|
- placeholder: Placeholder text (default: 'Select month')
|
|
- format: Month format (default: 'F Y')
|
|
- disabled: Alpine.js expression for disabled state
|
|
- required: Whether the field is required
|
|
#}
|
|
{% macro month_picker(id, x_model, label=none, placeholder='Select month', format='F Y', disabled=none, required=false) %}
|
|
<div class="mb-4">
|
|
{% if label %}
|
|
<label for="{{ id }}" class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1.5">
|
|
{{ label }}{% if required %} <span class="text-red-600">*</span>{% endif %}
|
|
</label>
|
|
{% endif %}
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
id="{{ id }}"
|
|
x-model="{{ x_model }}"
|
|
x-init="flatpickr($el, {
|
|
plugins: [new monthSelectPlugin({ shorthand: false, dateFormat: '{{ format }}', altFormat: '{{ format }}' })],
|
|
disableMobile: true,
|
|
onChange: function(selectedDates, dateStr) {
|
|
{{ x_model }} = dateStr;
|
|
}
|
|
})"
|
|
placeholder="{{ placeholder }}"
|
|
{% if required %}required{% endif %}
|
|
{% if disabled %}:disabled="{{ disabled }}"{% endif %}
|
|
class="block w-full pl-10 pr-4 py-2.5 text-sm text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 focus:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
readonly
|
|
>
|
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Inline Datepicker
|
|
=================
|
|
An inline calendar picker (always visible).
|
|
|
|
Parameters:
|
|
- id: Unique ID for the element
|
|
- x_model: Alpine.js x-model binding
|
|
- format: Date format (default: 'Y-m-d')
|
|
- min_date: Minimum selectable date
|
|
- max_date: Maximum selectable date
|
|
#}
|
|
{% macro inline_datepicker(id, x_model, format='Y-m-d', min_date=none, max_date=none) %}
|
|
<div
|
|
id="{{ id }}"
|
|
x-init="flatpickr($el, {
|
|
inline: true,
|
|
dateFormat: '{{ format }}',
|
|
{% if min_date %}minDate: '{{ min_date }}',{% endif %}
|
|
{% if max_date %}maxDate: '{{ max_date }}',{% endif %}
|
|
onChange: function(selectedDates, dateStr) {
|
|
{{ x_model }} = dateStr;
|
|
}
|
|
})"
|
|
class="flatpickr-inline"
|
|
></div>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Flatpickr Dark Mode Styles
|
|
==========================
|
|
Include this in your base template for dark mode support.
|
|
The dark theme CSS is loaded conditionally.
|
|
#}
|
|
{% macro datepicker_dark_mode_script() %}
|
|
<script>
|
|
// Apply dark theme to Flatpickr when dark mode is active
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const observer = new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(mutation) {
|
|
if (mutation.attributeName === 'class') {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
document.querySelectorAll('.flatpickr-calendar').forEach(calendar => {
|
|
calendar.classList.toggle('dark', isDark);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
observer.observe(document.documentElement, { attributes: true });
|
|
});
|
|
</script>
|
|
{% endmacro %}
|