diff --git a/app/templates/shared/macros/avatars.html b/app/templates/shared/macros/avatars.html new file mode 100644 index 00000000..0fb65301 --- /dev/null +++ b/app/templates/shared/macros/avatars.html @@ -0,0 +1,373 @@ +{# + Avatar Macros + ============= + Reusable avatar components for user profile images. + + Usage: + {% from 'shared/macros/avatars.html' import avatar, avatar_with_status, avatar_group, avatar_initials %} + + {# Basic avatar with image #} + {{ avatar(src='user.avatar_url', alt='user.name', size='md') }} + + {# Avatar with online status #} + {{ avatar_with_status(src='user.avatar', status='online', size='lg') }} + + {# Avatar group (stacked) #} + {% call avatar_group(max=3) %} + {{ avatar(src='url1', size='sm') }} + {{ avatar(src='url2', size='sm') }} + {{ avatar(src='url3', size='sm') }} + {% endcall %} + + {# Initials fallback #} + {{ avatar_initials(initials='JD', size='md', color='purple') }} +#} + + +{# + Avatar + ====== + A basic avatar component. + + Parameters: + - src: Image source (static string or Alpine.js expression with :src) + - alt: Alt text + - size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' (default: 'md') + - rounded: 'full' | 'lg' | 'md' (default: 'full') + - fallback_icon: Icon to show if no image (default: 'user') + - dynamic: Whether src is an Alpine.js expression (default: false) + - class_extra: Additional CSS classes +#} +{% macro avatar(src='', alt='', size='md', rounded='full', fallback_icon='user', dynamic=false, class_extra='') %} +{% set sizes = { + 'xs': 'w-6 h-6', + 'sm': 'w-8 h-8', + 'md': 'w-10 h-10', + 'lg': 'w-12 h-12', + 'xl': 'w-14 h-14', + '2xl': 'w-16 h-16' +} %} +{% set icon_sizes = { + 'xs': 'w-3 h-3', + 'sm': 'w-4 h-4', + 'md': 'w-5 h-5', + 'lg': 'w-6 h-6', + 'xl': 'w-7 h-7', + '2xl': 'w-8 h-8' +} %} +{% set roundeds = { + 'full': 'rounded-full', + 'lg': 'rounded-lg', + 'md': 'rounded-md' +} %} +
+ {% if dynamic %} + + + {% elif src %} + {{ alt }} + {% else %} + + {% endif %} +
+{% endmacro %} + + +{# + Avatar with Status + ================== + An avatar with an online/offline status indicator. + + Parameters: + - src: Image source + - status: 'online' | 'offline' | 'away' | 'busy' | Alpine.js expression + - size: Avatar size (default: 'md') + - alt: Alt text + - dynamic: Whether values are Alpine.js expressions (default: false) + - show_status: Whether to show status indicator (default: true) +#} +{% macro avatar_with_status(src='', status='online', size='md', alt='', dynamic=false, show_status=true) %} +{% set sizes = { + 'xs': 'w-6 h-6', + 'sm': 'w-8 h-8', + 'md': 'w-10 h-10', + 'lg': 'w-12 h-12', + 'xl': 'w-14 h-14', + '2xl': 'w-16 h-16' +} %} +{% set icon_sizes = { + 'xs': 'w-3 h-3', + 'sm': 'w-4 h-4', + 'md': 'w-5 h-5', + 'lg': 'w-6 h-6', + 'xl': 'w-7 h-7', + '2xl': 'w-8 h-8' +} %} +{% set indicator_sizes = { + 'xs': 'w-1.5 h-1.5', + 'sm': 'w-2 h-2', + 'md': 'w-2.5 h-2.5', + 'lg': 'w-3 h-3', + 'xl': 'w-3.5 h-3.5', + '2xl': 'w-4 h-4' +} %} +{% set indicator_positions = { + 'xs': 'bottom-0 right-0', + 'sm': 'bottom-0 right-0', + 'md': 'bottom-0 right-0', + 'lg': 'bottom-0.5 right-0.5', + 'xl': 'bottom-0.5 right-0.5', + '2xl': 'bottom-1 right-1' +} %} +{% set status_colors = { + 'online': 'bg-green-500', + 'offline': 'bg-gray-400', + 'away': 'bg-yellow-500', + 'busy': 'bg-red-500' +} %} +
+
+ {% if dynamic %} + + + {% elif src %} + {{ alt }} + {% else %} + + {% endif %} +
+ {% if show_status %} + + {% endif %} +
+{% endmacro %} + + +{# + Avatar Initials + =============== + An avatar showing initials instead of an image. + + Parameters: + - initials: 1-2 character initials (static or Alpine.js expression) + - size: Avatar size (default: 'md') + - color: 'gray' | 'purple' | 'blue' | 'green' | 'red' | 'yellow' | 'orange' (default: 'purple') + - dynamic: Whether initials is an Alpine.js expression (default: false) +#} +{% macro avatar_initials(initials='', size='md', color='purple', dynamic=false) %} +{% set sizes = { + 'xs': 'w-6 h-6 text-xs', + 'sm': 'w-8 h-8 text-xs', + 'md': 'w-10 h-10 text-sm', + 'lg': 'w-12 h-12 text-base', + 'xl': 'w-14 h-14 text-lg', + '2xl': 'w-16 h-16 text-xl' +} %} +{% set colors = { + 'gray': 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300', + 'purple': 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400', + 'blue': 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400', + 'green': 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400', + 'red': 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400', + 'yellow': 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400', + 'orange': 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400' +} %} +
+ {% if dynamic %} + + {% else %} + {{ initials }} + {% endif %} +
+{% endmacro %} + + +{# + Avatar with Fallback + ==================== + An avatar that shows initials when no image is available. + + Parameters: + - src: Image source (Alpine.js expression) + - initials: Fallback initials (Alpine.js expression) + - size: Avatar size + - color: Initials background color +#} +{% macro avatar_with_fallback(src, initials, size='md', color='purple') %} +{% set sizes = { + 'xs': 'w-6 h-6 text-xs', + 'sm': 'w-8 h-8 text-xs', + 'md': 'w-10 h-10 text-sm', + 'lg': 'w-12 h-12 text-base', + 'xl': 'w-14 h-14 text-lg', + '2xl': 'w-16 h-16 text-xl' +} %} +{% set colors = { + 'gray': 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300', + 'purple': 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400', + 'blue': 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400', + 'green': 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400', + 'red': 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400' +} %} +
+ + +
+{% endmacro %} + + +{# + Avatar Group + ============ + A stacked group of avatars. + + Parameters: + - max: Maximum number of avatars to show (default: 4) + - total_var: Alpine.js variable for total count (optional, for +N indicator) + - size: Avatar size (default: 'sm') +#} +{% macro avatar_group(max=4, total_var=none, size='sm') %} +{% set sizes = { + 'xs': 'w-6 h-6', + 'sm': 'w-8 h-8', + 'md': 'w-10 h-10', + 'lg': 'w-12 h-12' +} %} +{% set overlaps = { + 'xs': '-space-x-2', + 'sm': '-space-x-2', + 'md': '-space-x-3', + 'lg': '-space-x-4' +} %} +
+ {{ caller() }} + {% if total_var %} +
+ +
+ {% endif %} +
+{% endmacro %} + + +{# + Avatar Group Item + ================= + An avatar item for use within avatar_group. + Adds the ring styling for proper stacking. + + Parameters: + - src: Image source + - alt: Alt text + - size: Avatar size (default: 'sm') +#} +{% macro avatar_group_item(src='', alt='', size='sm', dynamic=false) %} +{% set sizes = { + 'xs': 'w-6 h-6', + 'sm': 'w-8 h-8', + 'md': 'w-10 h-10', + 'lg': 'w-12 h-12' +} %} +{% set icon_sizes = { + 'xs': 'w-3 h-3', + 'sm': 'w-4 h-4', + 'md': 'w-5 h-5', + 'lg': 'w-6 h-6' +} %} +
+ {% if dynamic %} + + + {% elif src %} + {{ alt }} + {% else %} + + {% endif %} +
+{% endmacro %} + + +{# + User Avatar Card + ================ + An avatar with name and optional subtitle/role. + + Parameters: + - src: Image source (Alpine.js expression) + - name: User name (Alpine.js expression) + - subtitle: Subtitle/role (Alpine.js expression, optional) + - size: Avatar size (default: 'md') + - href: Link URL (optional) +#} +{% macro user_avatar_card(src, name, subtitle=none, size='md', href=none) %} +{% set sizes = { + 'sm': 'w-8 h-8', + 'md': 'w-10 h-10', + 'lg': 'w-12 h-12' +} %} +{% set icon_sizes = { + 'sm': 'w-4 h-4', + 'md': 'w-5 h-5', + 'lg': 'w-6 h-6' +} %} +{% set text_sizes = { + 'sm': 'text-sm', + 'md': 'text-sm', + 'lg': 'text-base' +} %} +{% if href %} + +{% else %} +
+{% endif %} +
+ + +
+
+

+ {% if subtitle %} +

+ {% endif %} +
+{% if href %} +
+{% else %} +
+{% endif %} +{% endmacro %} diff --git a/app/templates/shared/macros/charts.html b/app/templates/shared/macros/charts.html new file mode 100644 index 00000000..c0be363e --- /dev/null +++ b/app/templates/shared/macros/charts.html @@ -0,0 +1,368 @@ +{# + Chart Macros + ============ + Reusable chart components using Chart.js with Alpine.js integration. + + Prerequisites: + Add Chart.js CDN to your base template: + + + Usage: + {% from 'shared/macros/charts.html' import chart_card, line_chart, bar_chart, doughnut_chart %} + + {# Line chart in a card #} + {{ chart_card('salesChart', 'Monthly Sales', 'line', chart_config) }} + + {# Standalone bar chart #} + {{ bar_chart('ordersChart', chart_data, height='300px') }} +#} + + +{# + Chart Card + ========== + A card container with a chart and optional dropdown menu. + + Parameters: + - id: Unique chart ID (used for canvas element) + - title: Card title + - chart_type: 'line' | 'bar' | 'doughnut' | 'pie' (default: 'line') + - height: Chart height (default: '300px') + - show_menu: Whether to show dropdown menu (default: true) + - subtitle: Optional subtitle text +#} +{% macro chart_card(id, title, chart_type='line', height='300px', show_menu=true, subtitle=none) %} +
+
+
+

{{ title }}

+ {% if subtitle %} +

{{ subtitle }}

+ {% endif %} +
+ {% if show_menu %} +
+ +
+ {{ caller() if caller is defined else '' }} +
+
+ {% endif %} +
+
+ +
+
+{% endmacro %} + + +{# + Line Chart + ========== + A standalone line chart canvas. + + Parameters: + - id: Unique chart ID + - height: Chart height (default: '300px') + - class_extra: Additional CSS classes +#} +{% macro line_chart(id, height='300px', class_extra='') %} +
+ +
+{% endmacro %} + + +{# + Bar Chart + ========= + A standalone bar chart canvas. + + Parameters: + - id: Unique chart ID + - height: Chart height (default: '300px') + - class_extra: Additional CSS classes +#} +{% macro bar_chart(id, height='300px', class_extra='') %} +
+ +
+{% endmacro %} + + +{# + Doughnut Chart + ============== + A standalone doughnut/pie chart canvas. + + Parameters: + - id: Unique chart ID + - size: Chart size (default: '200px') + - class_extra: Additional CSS classes +#} +{% macro doughnut_chart(id, size='200px', class_extra='') %} +
+ +
+{% endmacro %} + + +{# + Stats Chart Card + ================ + A card with a stat value and small sparkline chart. + + Parameters: + - id: Unique chart ID + - title: Stat title + - value_var: Alpine.js variable for the value + - trend_var: Alpine.js variable for trend ('up' | 'down' | 'neutral') + - trend_value_var: Alpine.js variable for trend percentage + - chart_height: Sparkline height (default: '60px') +#} +{% macro stats_chart_card(id, title, value_var, trend_var=none, trend_value_var=none, chart_height='60px') %} +
+
+

{{ title }}

+ {% if trend_var %} + + + + + + + + + + {% endif %} +
+

+
+ +
+
+{% endmacro %} + + +{# + Chart Legend + ============ + A custom chart legend. + + Parameters: + - items: List of legend items [{'label': '', 'color': ''}] + - layout: 'horizontal' | 'vertical' (default: 'horizontal') +#} +{% macro chart_legend(items, layout='horizontal') %} +
+ {% for item in items %} +
+ + {{ item.label }} +
+ {% endfor %} +
+{% endmacro %} + + +{# + Chart.js Configuration Helper + ============================= + JavaScript template for common chart configurations. + + Include this in a +{% endmacro %} + + +{# + Example Usage Comment + ===================== + Copy this to your page to see how to use the chart macros: + + {% from 'shared/macros/charts.html' import chart_card, chart_config_script %} + + {{ chart_card('monthlySales', 'Monthly Sales', 'line') }} + + {{ chart_config_script() }} + + +#} diff --git a/app/templates/shared/macros/datepicker.html b/app/templates/shared/macros/datepicker.html new file mode 100644 index 00000000..85822b27 --- /dev/null +++ b/app/templates/shared/macros/datepicker.html @@ -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: + + + + + 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='') %} +
+ {% if label %} + + {% endif %} +
+ +
+ + + +
+
+ {% if error %} +

+ {% endif %} +
+{% 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) %} +
+ {% if label %} + + {% endif %} +
+ +
+ + + +
+
+
+{% 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) %} +
+ {% if label %} + + {% endif %} +
+ +
+ + + +
+
+
+{% 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) %} +
+ {% if label %} + + {% endif %} +
+ +
+ + + +
+
+
+{% 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) %} +
+ {% if label %} + + {% endif %} +
+ +
+ + + +
+
+
+{% 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) %} +
+{% 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() %} + +{% endmacro %} diff --git a/app/templates/shared/macros/dropdowns.html b/app/templates/shared/macros/dropdowns.html new file mode 100644 index 00000000..0ef08b22 --- /dev/null +++ b/app/templates/shared/macros/dropdowns.html @@ -0,0 +1,350 @@ +{# + Dropdown Macros + =============== + Reusable dropdown menu components with Alpine.js integration. + + Usage: + {% from 'shared/macros/dropdowns.html' import dropdown, dropdown_menu, context_menu %} + + {# Basic dropdown #} + {% call dropdown('Actions', 'isDropdownOpen') %} + {{ dropdown_item('Edit', 'edit()', icon='pencil') }} + {{ dropdown_item('Delete', 'delete()', icon='trash', variant='danger') }} + {% endcall %} + + {# Context menu (3-dot icon) #} + {% call context_menu('itemMenu', 'isMenuOpen') %} + {{ dropdown_item('View', 'view()') }} + {{ dropdown_divider() }} + {{ dropdown_item('Delete', 'delete()', variant='danger') }} + {% endcall %} +#} + + +{# + Dropdown + ======== + A dropdown menu triggered by a button. + + Parameters: + - label: Button label + - open_var: Alpine.js variable for open state (default: 'isDropdownOpen') + - position: 'left' | 'right' (default: 'right') + - icon: Button icon (default: 'chevron-down') + - variant: 'primary' | 'secondary' | 'ghost' (default: 'secondary') + - size: 'sm' | 'md' (default: 'md') + - width: Dropdown width class (default: 'w-48') +#} +{% macro dropdown(label, open_var='isDropdownOpen', position='right', icon='chevron-down', variant='secondary', size='md', width='w-48') %} +{% set variants = { + 'primary': 'text-white bg-purple-600 hover:bg-purple-700 border-transparent', + 'secondary': 'text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 border-gray-300 dark:border-gray-600', + 'ghost': 'text-gray-600 dark:text-gray-400 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 border-transparent' +} %} +{% set sizes = { + 'sm': 'px-3 py-1.5 text-xs', + 'md': 'px-4 py-2 text-sm' +} %} +{% set positions = { + 'left': 'left-0', + 'right': 'right-0' +} %} +
+ + +
+ {{ caller() }} +
+
+{% endmacro %} + + +{# + Dropdown (External State) + ========================= + A dropdown that uses parent component's state. + + Parameters: + - label: Button label + - open_var: Alpine.js variable for open state + - All other params same as dropdown() +#} +{% macro dropdown_external(label, open_var='isDropdownOpen', position='right', icon='chevron-down', variant='secondary', size='md', width='w-48') %} +{% set variants = { + 'primary': 'text-white bg-purple-600 hover:bg-purple-700 border-transparent', + 'secondary': 'text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 border-gray-300 dark:border-gray-600', + 'ghost': 'text-gray-600 dark:text-gray-400 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 border-transparent' +} %} +{% set sizes = { + 'sm': 'px-3 py-1.5 text-xs', + 'md': 'px-4 py-2 text-sm' +} %} +{% set positions = { + 'left': 'left-0', + 'right': 'right-0' +} %} +
+ + +
+ {{ caller() }} +
+
+{% endmacro %} + + +{# + Context Menu (3-dot menu) + ========================= + An icon-only dropdown menu, commonly used for row actions. + + Parameters: + - id: Unique ID for the menu + - open_var: Alpine.js variable for open state (default: 'isMenuOpen') + - position: 'left' | 'right' (default: 'right') + - icon: Icon name (default: 'dots-vertical') + - width: Dropdown width class (default: 'w-40') +#} +{% macro context_menu(id='contextMenu', open_var='isMenuOpen', position='right', icon='dots-vertical', width='w-40') %} +{% set positions = { + 'left': 'left-0', + 'right': 'right-0' +} %} +
+ + +
+ {{ caller() }} +
+
+{% endmacro %} + + +{# + Context Menu (External State) + ============================= + A context menu that uses parent component's state. + + Parameters: + - open_var: Alpine.js variable for open state + - position: 'left' | 'right' (default: 'right') + - icon: Icon name (default: 'dots-vertical') + - width: Dropdown width class (default: 'w-40') +#} +{% macro context_menu_external(open_var='isMenuOpen', position='right', icon='dots-vertical', width='w-40') %} +{% set positions = { + 'left': 'left-0', + 'right': 'right-0' +} %} +
+ + +
+ {{ caller() }} +
+
+{% endmacro %} + + +{# + Dropdown Item + ============= + An item within a dropdown menu. + + Parameters: + - label: Item label + - onclick: Alpine.js click handler + - icon: Icon name (optional) + - variant: 'default' | 'danger' (default: 'default') + - href: URL if this should be a link + - disabled: Whether the item is disabled +#} +{% macro dropdown_item(label, onclick=none, icon=none, variant='default', href=none, disabled=false) %} +{% set variants = { + 'default': 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white', + 'danger': 'text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-700 dark:hover:text-red-300' +} %} +{% if href %} + + {% if icon %} + + {% endif %} + {{ label }} + +{% else %} + +{% endif %} +{% endmacro %} + + +{# + Dropdown Divider + ================ + A horizontal divider between dropdown items. +#} +{% macro dropdown_divider() %} +
+{% endmacro %} + + +{# + Dropdown Header + =============== + A non-clickable header within a dropdown. + + Parameters: + - text: Header text +#} +{% macro dropdown_header(text) %} +
+ {{ text }} +
+{% endmacro %} + + +{# + Select Dropdown + =============== + A dropdown that acts as a select input. + + Parameters: + - label: Button label when nothing selected + - selected_var: Alpine.js variable for selected value + - selected_label_var: Alpine.js variable for selected label display + - open_var: Alpine.js variable for open state + - placeholder: Placeholder when nothing selected + - width: Dropdown width class (default: 'w-full') +#} +{% macro select_dropdown(label='', selected_var='selected', selected_label_var='selectedLabel', open_var='isSelectOpen', placeholder='Select...', width='w-full') %} +
+ + +
+ {{ caller() }} +
+
+{% endmacro %} + + +{# + Select Option + ============= + An option within a select dropdown. + + Parameters: + - value: Option value + - label: Option label + - selected_var: Alpine.js variable for selected value + - selected_label_var: Alpine.js variable for selected label + - open_var: Alpine.js variable to close dropdown +#} +{% macro select_option(value, label, selected_var='selected', selected_label_var='selectedLabel', open_var='isSelectOpen') %} + +{% endmacro %} diff --git a/app/templates/shared/macros/forms.html b/app/templates/shared/macros/forms.html index 92352fe7..2cbe6edf 100644 --- a/app/templates/shared/macros/forms.html +++ b/app/templates/shared/macros/forms.html @@ -4,10 +4,12 @@ Reusable form input components. Usage: - {% from 'shared/macros/forms.html' import form_input, form_select, form_textarea, form_checkbox %} + {% from 'shared/macros/forms.html' import form_input, form_select, form_textarea, form_checkbox, password_input, input_with_icon %} {{ form_input('Email', 'email', 'formData.email', type='email', required=true) }} {{ form_select('Status', 'formData.status', [{'value': 'active', 'label': 'Active'}]) }} {{ form_textarea('Description', 'formData.description', rows=4) }} + {{ password_input('Password', 'formData.password', required=true) }} + {{ input_with_icon('Search', 'query', 'search', icon='search', icon_position='left') }} #} @@ -330,3 +332,215 @@ {% endfor %} {% endmacro %} + + +{# + Password Input + ============== + A password input with show/hide toggle. + + Parameters: + - label: Field label + - x_model: Alpine.js x-model binding + - name: Input name attribute + - placeholder: Placeholder text (default: 'Enter password') + - required: Whether the field is required (default: false) + - disabled: Alpine.js expression for disabled state + - error: Alpine.js expression for error message + - help: Help text shown below the input + - minlength: Minimum password length + - autocomplete: Autocomplete attribute (default: 'current-password') + - show_strength: Whether to show password strength indicator (default: false) +#} +{% macro password_input(label, x_model, name='password', placeholder='Enter password', required=false, disabled=none, error=none, help=none, minlength=none, autocomplete='current-password', show_strength=false) %} +
+ + {% if show_strength %} +
+
+
+
+
+
+

+
+ {% endif %} +
+{% endmacro %} + + +{# + Input with Icon + =============== + A text input with an icon on the left or right. + + Parameters: + - label: Field label + - x_model: Alpine.js x-model binding + - name: Input name attribute + - icon: Icon name (uses $icon helper) + - icon_position: 'left' | 'right' (default: 'left') + - type: Input type (default: 'text') + - placeholder: Placeholder text + - required: Whether the field is required + - disabled: Alpine.js expression for disabled state + - error: Alpine.js expression for error message + - on_click_icon: Alpine.js handler when icon is clicked (makes icon a button) +#} +{% macro input_with_icon(label, x_model, name, icon, icon_position='left', type='text', placeholder='', required=false, disabled=none, error=none, on_click_icon=none) %} + +{% endmacro %} + + +{# + File Input + ========== + A styled file input with drag and drop support. + + Parameters: + - label: Field label + - name: Input name attribute + - accept: Accepted file types (e.g., 'image/*', '.pdf,.doc') + - multiple: Allow multiple files (default: false) + - max_size: Maximum file size in MB (for display only) + - on_change: Alpine.js handler when files are selected + - help: Help text +#} +{% macro file_input(label, name, accept='*', multiple=false, max_size=none, on_change=none, help=none) %} +
+ +
+ +
+ + + +

+ Click to upload or drag and drop +

+ {% if help %} +

{{ help }}

+ {% elif max_size %} +

Max file size: {{ max_size }}MB

+ {% endif %} +
+
+
+{% endmacro %} diff --git a/app/templates/shared/macros/modals.html b/app/templates/shared/macros/modals.html new file mode 100644 index 00000000..6dddcf01 --- /dev/null +++ b/app/templates/shared/macros/modals.html @@ -0,0 +1,477 @@ +{# + Modal Macros + ============ + Reusable modal dialog components with Alpine.js integration. + + Usage: + {% from 'shared/macros/modals.html' import modal, confirm_modal, form_modal %} + + {# Basic modal #} + {% call modal('editModal', 'Edit User', 'isEditModalOpen') %} +

Modal content here

+ {% endcall %} + + {# Confirmation modal #} + {{ confirm_modal('deleteModal', 'Delete User', 'Are you sure?', 'deleteUser()', 'isDeleteModalOpen') }} + + Required Alpine.js: + x-data="{ isModalOpen: false }" +#} + + +{# + Modal + ===== + A flexible modal dialog component. + + Parameters: + - id: Unique modal ID + - title: Modal title + - show_var: Alpine.js variable controlling visibility (default: 'isModalOpen') + - size: 'sm' | 'md' | 'lg' | 'xl' | 'full' (default: 'md') + - show_close: Whether to show close button (default: true) + - show_footer: Whether to show footer slot (default: true) + - close_on_backdrop: Close when clicking backdrop (default: true) + - close_on_escape: Close on Escape key (default: true) +#} +{% macro modal(id, title, show_var='isModalOpen', size='md', show_close=true, show_footer=true, close_on_backdrop=true, close_on_escape=true) %} +{% set sizes = { + 'sm': 'max-w-sm', + 'md': 'max-w-lg', + 'lg': 'max-w-2xl', + 'xl': 'max-w-4xl', + 'full': 'max-w-full mx-4' +} %} + +{% endmacro %} + + +{# + Modal Simple + ============ + A simpler modal without the caller pattern - just pass content. + + Parameters: + - id: Unique modal ID + - title: Modal title + - show_var: Alpine.js variable controlling visibility + - size: Modal size + - content: HTML content for the modal body +#} +{% macro modal_simple(id, title, show_var='isModalOpen', size='md') %} +{% set sizes = { + 'sm': 'max-w-sm', + 'md': 'max-w-lg', + 'lg': 'max-w-2xl', + 'xl': 'max-w-4xl' +} %} + +{% endmacro %} + + +{# + Confirm Modal + ============= + A confirmation dialog for destructive actions. + + Parameters: + - id: Unique modal ID + - title: Modal title + - message: Confirmation message + - confirm_action: Alpine.js action to execute on confirm + - show_var: Alpine.js variable controlling visibility + - confirm_text: Confirm button text (default: 'Confirm') + - cancel_text: Cancel button text (default: 'Cancel') + - variant: 'danger' | 'warning' | 'info' (default: 'danger') + - icon: Icon name (optional, auto-selected based on variant) +#} +{% macro confirm_modal(id, title, message, confirm_action, show_var='isConfirmModalOpen', confirm_text='Confirm', cancel_text='Cancel', variant='danger', icon=none) %} +{% set variants = { + 'danger': { + 'icon_bg': 'bg-red-100 dark:bg-red-900/30', + 'icon_color': 'text-red-600 dark:text-red-400', + 'btn_class': 'bg-red-600 hover:bg-red-700 focus:ring-red-500' + }, + 'warning': { + 'icon_bg': 'bg-yellow-100 dark:bg-yellow-900/30', + 'icon_color': 'text-yellow-600 dark:text-yellow-400', + 'btn_class': 'bg-yellow-600 hover:bg-yellow-700 focus:ring-yellow-500' + }, + 'info': { + 'icon_bg': 'bg-blue-100 dark:bg-blue-900/30', + 'icon_color': 'text-blue-600 dark:text-blue-400', + 'btn_class': 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-500' + } +} %} +{% set icons = { + 'danger': 'exclamation-triangle', + 'warning': 'exclamation', + 'info': 'information-circle' +} %} +{% set modal_icon = icon if icon else icons[variant] %} +{% set style = variants[variant] %} + + +{% endmacro %} + + +{# + Form Modal + ========== + A modal optimized for forms with loading state support. + + Parameters: + - id: Unique modal ID + - title: Modal title + - show_var: Alpine.js variable controlling visibility + - submit_action: Alpine.js action for form submission + - submit_text: Submit button text (default: 'Save') + - loading_var: Alpine.js variable for loading state (default: 'saving') + - loading_text: Text shown while loading (default: 'Saving...') + - size: Modal size (default: 'md') +#} +{% macro form_modal(id, title, show_var='isFormModalOpen', submit_action='submitForm()', submit_text='Save', loading_var='saving', loading_text='Saving...', size='md') %} +{% set sizes = { + 'sm': 'max-w-sm', + 'md': 'max-w-lg', + 'lg': 'max-w-2xl', + 'xl': 'max-w-4xl' +} %} + +{% endmacro %} + + +{# + Slide-over Panel + ================ + A side panel that slides in from the right. + + Parameters: + - id: Unique panel ID + - title: Panel title + - show_var: Alpine.js variable controlling visibility + - width: 'sm' | 'md' | 'lg' | 'xl' (default: 'md') +#} +{% macro slide_over(id, title, show_var='isPanelOpen', width='md') %} +{% set widths = { + 'sm': 'max-w-sm', + 'md': 'max-w-md', + 'lg': 'max-w-lg', + 'xl': 'max-w-xl' +} %} + +{% endmacro %}