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>
This commit is contained in:
355
app/templates/shared/macros/datepicker.html
Normal file
355
app/templates/shared/macros/datepicker.html
Normal file
@@ -0,0 +1,355 @@
|
||||
{#
|
||||
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 %}
|
||||
Reference in New Issue
Block a user