Files
orion/app/templates/shared/macros/datepicker.html
Samir Boulahtit b0db4d26d8 feat: add advanced UI component macros
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>
2025-12-06 19:05:13 +01:00

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