# Jinja Macros Library **Version:** 1.0 **Last Updated:** December 2024 **Location:** `app/templates/shared/macros/` --- ## Overview The Jinja macros library provides reusable UI components for building consistent admin pages. All macros are built with: - **Tailwind CSS** for styling - **Alpine.js** for interactivity - **Dark mode** support built-in - **Accessibility** best practices ### Quick Start ```jinja {% from 'shared/macros/buttons.html' import btn_primary %} {% from 'shared/macros/forms.html' import form_input %} {% from 'shared/macros/modals.html' import confirm_modal %} {{ btn_primary('Save Changes', onclick='saveForm()') }} {{ form_input('Email', 'email', 'formData.email', type='email', required=true) }} {{ confirm_modal('deleteModal', 'Delete Item', 'Are you sure?', 'deleteItem()', 'showDeleteModal') }} ``` --- ## Available Macro Files | File | Macros | Description | |------|--------|-------------| | [alerts.html](#alerts) | 5 | Loading states, error alerts, toasts | | [avatars.html](#avatars) | 7 | User avatars with status indicators | | [badges.html](#badges) | 8 | Status badges, role badges, counts | | [buttons.html](#buttons) | 11 | Primary, secondary, danger, icon buttons | | [cards.html](#cards) | 7 | Stats cards, info cards, filter cards | | [charts.html](#charts) | 7 | Chart.js integration with Card wrappers | | [datepicker.html](#datepicker) | 6 | Flatpickr date/time pickers | | [dropdowns.html](#dropdowns) | 8 | Dropdown menus, context menus | | [forms.html](#forms) | 12 | Input fields, selects, checkboxes | | [headers.html](#headers) | 6 | Page headers, breadcrumbs, tabs | | [modals.html](#modals) | 5 + patterns | Modal dialogs, slide-overs, detail patterns | | [pagination.html](#pagination) | 2 | Table pagination controls | | [tables.html](#tables) | 10 | Table wrappers, cells, empty states | **Total: 94 macros** --- ## Alerts **File:** `shared/macros/alerts.html` ```jinja {% from 'shared/macros/alerts.html' import loading_state, error_state, alert, toast %} ``` ### loading_state Shows a centered loading spinner with message. ```jinja {{ loading_state(show_condition='loading', message='Loading data...') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `show_condition` | string | `'loading'` | Alpine.js condition | | `message` | string | `'Loading...'` | Loading message | ### error_state Shows an error alert with icon. ```jinja {{ error_state(show_condition='error', error_var='errorMessage') }} ``` ### alert Static alert box with variants. ```jinja {{ alert('Operation completed successfully!', variant='success', dismissible=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `message` | string | required | Alert message | | `variant` | string | `'info'` | `'success'`, `'warning'`, `'error'`, `'info'` | | `dismissible` | bool | `false` | Show close button | | `icon` | string | auto | Icon name | ### toast Toast notification (auto-dismiss). ```jinja {{ toast('Item saved!', 'success') }} ``` --- ## Avatars **File:** `shared/macros/avatars.html` ```jinja {% from 'shared/macros/avatars.html' import avatar, avatar_with_status, avatar_initials, avatar_group %} ``` ### avatar Basic avatar component. ```jinja {# Static image #} {{ avatar(src='/images/user.jpg', alt='John Doe', size='lg') }} {# Dynamic with Alpine.js #} {{ avatar(src='user.avatar_url', alt='user.name', size='md', dynamic=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `src` | string | `''` | Image URL or Alpine.js expression | | `alt` | string | `''` | Alt text | | `size` | string | `'md'` | `'xs'`, `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` | | `dynamic` | bool | `false` | Whether src is Alpine.js expression | | `fallback_icon` | string | `'user'` | Icon when no image | **Sizes:** - `xs`: 24px - `sm`: 32px - `md`: 40px - `lg`: 48px - `xl`: 56px - `2xl`: 64px ### avatar_with_status Avatar with online/offline indicator. ```jinja {{ avatar_with_status(src='user.avatar', status='online', size='md', dynamic=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | `'online'` | `'online'`, `'offline'`, `'away'`, `'busy'` | ### avatar_initials Avatar showing initials. ```jinja {{ avatar_initials(initials='JD', size='md', color='purple') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `initials` | string | required | 1-2 characters | | `color` | string | `'purple'` | `'gray'`, `'purple'`, `'blue'`, `'green'`, `'red'`, `'yellow'`, `'orange'` | ### avatar_group Stacked avatar group. ```jinja {% call avatar_group(max=4, total_var='team.length', size='sm') %} {% endcall %} ``` ### user_avatar_card Avatar with name and subtitle. ```jinja {{ user_avatar_card(src='user.avatar', name='user.name', subtitle='user.role', size='md') }} ``` --- ## Badges **File:** `shared/macros/badges.html` ```jinja {% from 'shared/macros/badges.html' import badge, status_badge, role_badge, severity_badge %} ``` ### badge Generic badge. ```jinja {{ badge('New', variant='success', icon='check') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `text` | string | required | Badge text | | `variant` | string | `'gray'` | `'gray'`, `'green'`, `'red'`, `'yellow'`, `'blue'`, `'purple'` | | `icon` | string | `none` | Optional icon | | `size` | string | `'md'` | `'sm'`, `'md'` | ### status_badge Dynamic status badge based on boolean. ```jinja {{ status_badge(condition='item.is_active', true_text='Active', false_text='Inactive') }} ``` ### role_badge User role badge. ```jinja {{ role_badge(role='item.role') }} ``` Automatically colors: admin=red, manager=purple, staff=blue, viewer=gray ### severity_badge Severity level badge. ```jinja {{ severity_badge(severity='violation.severity') }} ``` Levels: critical=red, error=orange, warning=yellow, info=blue ### order_status_badge Order status badge. ```jinja {{ order_status_badge(status='order.status') }} ``` Statuses: pending, processing, shipped, delivered, cancelled, refunded ### count_badge Numeric count badge (notification style). ```jinja {{ count_badge(count='notifications.length', max=99) }} ``` --- ## Buttons **File:** `shared/macros/buttons.html` ```jinja {% from 'shared/macros/buttons.html' import btn, btn_primary, btn_secondary, btn_danger, action_button %} ``` ### btn Base button macro. ```jinja {{ btn('Click Me', variant='primary', size='md', icon='plus', onclick='doSomething()') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Button text | | `variant` | string | `'primary'` | `'primary'`, `'secondary'`, `'danger'`, `'success'`, `'ghost'` | | `size` | string | `'md'` | `'sm'`, `'md'`, `'lg'` | | `icon` | string | `none` | Icon name | | `icon_position` | string | `'left'` | `'left'`, `'right'` | | `onclick` | string | `none` | Alpine.js click handler | | `href` | string | `none` | Makes it a link | | `disabled` | string | `none` | Alpine.js disabled condition | | `loading` | string | `none` | Alpine.js loading condition | | `type` | string | `'button'` | Button type | ### Convenience Variants ```jinja {{ btn_primary('Save', icon='check', onclick='save()') }} {{ btn_secondary('Cancel', onclick='cancel()') }} {{ btn_danger('Delete', icon='trash', onclick='delete()') }} {{ btn_success('Approve', onclick='approve()') }} ``` ### action_button Icon-only action button for tables. ```jinja {{ action_button(icon='edit', onclick='edit(item)', variant='primary', title='Edit') }} {{ action_button(icon='trash', onclick='delete(item)', variant='danger', title='Delete') }} ``` ### action_button_group Group of action buttons. ```jinja {% call action_button_group() %} {{ action_button(icon='eye', href="'/items/' + item.id", variant='info', title='View') }} {{ action_button(icon='edit', onclick='edit(item)', variant='primary', title='Edit') }} {{ action_button(icon='trash', onclick='confirmDelete(item)', variant='danger', title='Delete') }} {% endcall %} ``` ### back_button Back navigation button. ```jinja {{ back_button(href='/admin/items', label='Back to List') }} ``` ### submit_button Form submit button with loading state. ```jinja {{ submit_button(label='Save Changes', loading_var='saving', loading_text='Saving...') }} ``` --- ## Cards **File:** `shared/macros/cards.html` ```jinja {% from 'shared/macros/cards.html' import stat_card, card, info_card, filter_card %} ``` ### stat_card Statistics card with icon and value. ```jinja {{ stat_card(icon='users', label='Total Users', value='stats.totalUsers', color='purple') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `icon` | string | required | Icon name | | `label` | string | required | Stat label | | `value` | string | required | Alpine.js expression | | `color` | string | `'orange'` | `'orange'`, `'green'`, `'blue'`, `'purple'`, `'red'`, `'yellow'`, `'teal'` | | `format` | string | `none` | Format function (e.g., `'formatCurrency'`) | ### stats_grid Grid container for stat cards. ```jinja {% call stats_grid(columns=4) %} {{ stat_card(icon='users', label='Users', value='stats.users', color='blue') }} {{ stat_card(icon='shopping-cart', label='Orders', value='stats.orders', color='green') }} {{ stat_card(icon='currency-dollar', label='Revenue', value='stats.revenue', color='purple', format='formatCurrency') }} {{ stat_card(icon='chart-bar', label='Growth', value='stats.growth', color='orange') }} {% endcall %} ``` ### card Basic card container. ```jinja {% call card(title='User Details', subtitle='View and edit user information') %}

Card content here

{% endcall %} ``` ### info_card Card with icon and description. ```jinja {{ info_card(icon='document', title='Documentation', description='View the full docs', color='blue', href='/docs') }} ``` ### filter_card Card for search and filter controls. ```jinja {% call filter_card() %} {{ search_input(x_model='filters.search', on_input='debouncedSearch()') }} {{ filter_select(x_model='filters.status', options=[...]) }} {% endcall %} ``` --- ## Charts **File:** `shared/macros/charts.html` **Prerequisites:** Requires Chart.js. See [CDN Fallback Strategy](../cdn-fallback-strategy.md). ```jinja {% from 'shared/macros/charts.html' import chart_card, line_chart, bar_chart, doughnut_chart, chart_config_script %} ``` ### Loading Chart.js In your page template: ```jinja {% block chartjs_script %} {% from 'shared/includes/optional-libs.html' import chartjs_loader %} {{ chartjs_loader() }} {% endblock %} ``` ### chart_card Chart in a card with title and optional menu. ```jinja {{ chart_card('salesChart', 'Monthly Sales', 'line', height='300px') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `id` | string | required | Canvas element ID | | `title` | string | required | Card title | | `chart_type` | string | `'line'` | `'line'`, `'bar'`, `'doughnut'`, `'pie'` | | `height` | string | `'300px'` | Chart height | | `show_menu` | bool | `true` | Show dropdown menu | ### Standalone Charts ```jinja {{ line_chart('revenueChart', height='250px') }} {{ bar_chart('ordersChart', height='300px') }} {{ doughnut_chart('categoryChart', size='200px') }} ``` ### chart_config_script Include Chart.js configuration helpers. ```jinja {{ chart_config_script() }} ``` **Available helper functions:** - `createChart(id, type, data, options)` - `createLineChart(id, labels, datasets, options)` - `createBarChart(id, labels, datasets, options)` - `createDoughnutChart(id, labels, data, colors, options)` **Color presets:** `chartColors.purple`, `chartColors.blue`, `chartColors.green`, `chartColors.red`, `chartColors.yellow`, `chartColors.gray` --- ## Datepicker **File:** `shared/macros/datepicker.html` **Prerequisites:** Requires Flatpickr. See [CDN Fallback Strategy](../cdn-fallback-strategy.md). ```jinja {% from 'shared/macros/datepicker.html' import datepicker, daterange_picker, datetime_picker, time_picker %} ``` ### Loading Flatpickr In your page template: ```jinja {% block flatpickr_css %} {% from 'shared/includes/optional-libs.html' import flatpickr_css_loader %} {{ flatpickr_css_loader() }} {% endblock %} {% block flatpickr_script %} {% from 'shared/includes/optional-libs.html' import flatpickr_loader %} {{ flatpickr_loader() }} {% endblock %} ``` ### datepicker Single date picker. ```jinja {{ datepicker('startDate', 'formData.startDate', label='Start Date', required=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `id` | string | required | Input ID | | `x_model` | string | required | Alpine.js model | | `label` | string | `none` | Field label | | `placeholder` | string | `'Select date'` | Placeholder text | | `format` | string | `'Y-m-d'` | Date format | | `min_date` | string | `none` | Minimum date | | `max_date` | string | `none` | Maximum date | | `required` | bool | `false` | Required field | ### daterange_picker Date range picker. ```jinja {{ daterange_picker('dateRange', 'filters.dateRange', label='Date Range') }} ``` ### datetime_picker Date and time picker. ```jinja {{ datetime_picker('scheduledAt', 'formData.scheduledAt', label='Schedule For', minute_increment=15) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `time_24hr` | bool | `true` | Use 24-hour format | | `minute_increment` | int | `5` | Minute step | ### time_picker Time-only picker. ```jinja {{ time_picker('startTime', 'formData.startTime', label='Start Time') }} ``` --- ## Dropdowns **File:** `shared/macros/dropdowns.html` ```jinja {% from 'shared/macros/dropdowns.html' import dropdown, context_menu, dropdown_item, dropdown_divider %} ``` ### dropdown Button with dropdown menu. ```jinja {% call dropdown('Actions', position='right') %} {{ dropdown_item('Edit', onclick='edit()', icon='pencil') }} {{ dropdown_item('Duplicate', onclick='duplicate()', icon='document-duplicate') }} {{ dropdown_divider() }} {{ dropdown_item('Delete', onclick='delete()', icon='trash', variant='danger') }} {% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Button label | | `position` | string | `'right'` | `'left'`, `'right'` | | `variant` | string | `'secondary'` | `'primary'`, `'secondary'`, `'ghost'` | | `icon` | string | `'chevron-down'` | Button icon | | `width` | string | `'w-48'` | Dropdown width | ### context_menu Three-dot context menu (for table rows). ```jinja {% call context_menu() %} {{ dropdown_item('View', href="'/items/' + item.id", icon='eye') }} {{ dropdown_item('Edit', onclick='edit(item)', icon='pencil') }} {{ dropdown_divider() }} {{ dropdown_item('Delete', onclick='confirmDelete(item)', icon='trash', variant='danger') }} {% endcall %} ``` ### dropdown_item Menu item. ```jinja {{ dropdown_item('Edit', onclick='edit()', icon='pencil') }} {{ dropdown_item('View Details', href='/details/123', icon='eye') }} {{ dropdown_item('Delete', onclick='delete()', icon='trash', variant='danger') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Item label | | `onclick` | string | `none` | Click handler | | `href` | string | `none` | Link URL | | `icon` | string | `none` | Icon name | | `variant` | string | `'default'` | `'default'`, `'danger'` | | `disabled` | bool | `false` | Disabled state | ### select_dropdown Custom select dropdown. ```jinja {% call select_dropdown(placeholder='Select status...') %} {{ select_option('active', 'Active') }} {{ select_option('inactive', 'Inactive') }} {{ select_option('pending', 'Pending') }} {% endcall %} ``` --- ## Forms **File:** `shared/macros/forms.html` ```jinja {% from 'shared/macros/forms.html' import form_input, form_select, form_textarea, form_checkbox, form_toggle, password_input, search_input %} ``` ### form_input Standard text input. ```jinja {{ form_input('Email', 'email', 'formData.email', type='email', required=true, placeholder='Enter email') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Field label | | `name` | string | required | Input name | | `x_model` | string | required | Alpine.js model | | `type` | string | `'text'` | Input type | | `placeholder` | string | `''` | Placeholder | | `required` | bool | `false` | Required | | `disabled` | string | `none` | Alpine.js disabled condition | | `error` | string | `none` | Alpine.js error message | | `help` | string | `none` | Help text | | `maxlength` | int | `none` | Max length | ### password_input Password input with show/hide toggle. ```jinja {{ password_input('Password', 'formData.password', required=true, show_strength=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `show_strength` | bool | `false` | Show strength indicator | | `minlength` | int | `none` | Minimum length | ### input_with_icon Input with icon. ```jinja {{ input_with_icon('Website', 'formData.url', 'url', icon='globe', placeholder='https://...') }} {{ input_with_icon('Search', 'query', 'q', icon='search', on_click_icon='search()') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `icon` | string | required | Icon name | | `icon_position` | string | `'left'` | `'left'`, `'right'` | | `on_click_icon` | string | `none` | Makes icon clickable | ### form_select Select dropdown. ```jinja {{ form_select('Status', 'formData.status', [ {'value': 'active', 'label': 'Active'}, {'value': 'inactive', 'label': 'Inactive'} ], required=true) }} ``` ### form_select_dynamic Select with Alpine.js options. ```jinja {{ form_select_dynamic('Category', 'formData.categoryId', 'categories', value_key='id', label_key='name') }} ``` ### form_textarea Textarea input. ```jinja {{ form_textarea('Description', 'formData.description', rows=4, maxlength=500) }} ``` ### form_checkbox Checkbox input. ```jinja {{ form_checkbox('Subscribe to newsletter', 'formData.subscribe', help='We will send weekly updates') }} ``` ### form_toggle Toggle switch. ```jinja {{ form_toggle('Enable notifications', 'formData.notifications') }} ``` ### form_radio_group Radio button group. ```jinja {{ form_radio_group('Priority', 'priority', 'formData.priority', [ {'value': 'low', 'label': 'Low'}, {'value': 'medium', 'label': 'Medium'}, {'value': 'high', 'label': 'High'} ], inline=true) }} ``` ### search_input Search input with icon. ```jinja {{ search_input(x_model='filters.search', placeholder='Search items...', on_input='debouncedSearch()') }} ``` ### filter_select Compact filter select (no label). ```jinja {{ filter_select(x_model='filters.status', options=[ {'value': 'active', 'label': 'Active'}, {'value': 'inactive', 'label': 'Inactive'} ], placeholder='All Statuses', on_change='applyFilters()') }} ``` ### file_input Drag and drop file upload. ```jinja {{ file_input('Upload Image', 'image', accept='image/*', max_size=5, on_change='handleFileSelect($event)') }} ``` --- ## Headers **File:** `shared/macros/headers.html` ```jinja {% from 'shared/macros/headers.html' import page_header, section_header, breadcrumbs, tab_header %} ``` ### page_header Page title with optional action button. ```jinja {{ page_header('User Management', subtitle='Manage all users', action_label='Add User', action_url='/admin/users/create', action_icon='plus') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `title` | string | required | Page title | | `subtitle` | string | `none` | Subtitle | | `action_label` | string | `none` | Action button text | | `action_url` | string | `none` | Action button URL | | `action_onclick` | string | `none` | Action click handler | | `action_icon` | string | `'plus'` | Action button icon | | `back_url` | string | `none` | Back button URL | ### page_header_dynamic Page header with Alpine.js title. ```jinja {{ page_header_dynamic(title_var='pageTitle', back_url='/admin/items') }} ``` ### section_header Section header within a page. ```jinja {{ section_header('Account Settings', subtitle='Update your account preferences', icon='cog', action_label='Edit', action_onclick='openSettings()') }} ``` ### breadcrumbs Navigation breadcrumbs. ```jinja {{ breadcrumbs([ {'label': 'Home', 'url': '/admin'}, {'label': 'Users', 'url': '/admin/users'}, {'label': 'John Doe'} ]) }} ``` ### tab_header Tab navigation. ```jinja {{ tab_header([ {'id': 'general', 'label': 'General', 'icon': 'cog'}, {'id': 'security', 'label': 'Security', 'icon': 'shield-check'}, {'id': 'notifications', 'label': 'Notifications', 'icon': 'bell'} ], active_var='activeTab') }} ``` --- ## Modals **File:** `shared/macros/modals.html` ```jinja {% from 'shared/macros/modals.html' import modal, confirm_modal, form_modal, slide_over %} ``` ### modal Basic modal dialog. ```jinja {% call modal('editModal', 'Edit Item', show_var='isEditModalOpen', size='lg') %}

Modal content here

{% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `id` | string | required | Modal ID | | `title` | string | required | Modal title | | `show_var` | string | `'isModalOpen'` | Alpine.js visibility variable | | `size` | string | `'md'` | `'sm'`, `'md'`, `'lg'`, `'xl'`, `'full'` | | `show_close` | bool | `true` | Show close button | | `show_footer` | bool | `true` | Show footer | | `close_on_backdrop` | bool | `true` | Close on backdrop click | | `close_on_escape` | bool | `true` | Close on Escape key | ### confirm_modal Confirmation dialog for destructive actions. ```jinja {{ confirm_modal( 'deleteModal', 'Delete User', 'Are you sure you want to delete this user? This action cannot be undone.', 'deleteUser()', 'isDeleteModalOpen', confirm_text='Delete', variant='danger' ) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `confirm_action` | string | required | Alpine.js action on confirm | | `confirm_text` | string | `'Confirm'` | Confirm button text | | `cancel_text` | string | `'Cancel'` | Cancel button text | | `variant` | string | `'danger'` | `'danger'`, `'warning'`, `'info'` | ### form_modal Modal optimized for forms with loading state. ```jinja {% call form_modal('createModal', 'Create Item', submit_action='createItem()', loading_var='saving') %} {{ form_input('Name', 'name', 'formData.name', required=true) }} {{ form_textarea('Description', 'formData.description') }} {% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `submit_action` | string | `'submitForm()'` | Form submit handler | | `submit_text` | string | `'Save'` | Submit button text | | `loading_var` | string | `'saving'` | Loading state variable | | `loading_text` | string | `'Saving...'` | Loading button text | ### slide_over Side panel that slides in from the right. ```jinja {% call slide_over('detailsPanel', 'Item Details', show_var='isPanelOpen', width='lg') %}

Panel content here

{% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `width` | string | `'md'` | `'sm'`, `'md'`, `'lg'`, `'xl'` | ### Details Modal Pattern (Inline) For complex detail views (job details, log entries, etc.), use inline modals with this pattern structure: **Structure:** 1. **Header** - Icon with status-based coloring, title, subtitle, status badge, close button 2. **Stats Cards** - Grid of colored stat cards (imported, updated, errors, total) 3. **Details Table** - Key-value pairs with icons in a bordered table 4. **Content Sections** - Message, exception, stack trace (conditional) 5. **Footer** - Close button with proper styling **Example implementations:** - Job Details Modal: `app/templates/shared/macros/modals.html` → `job_details_modal` - Log Details Modal: `app/templates/admin/logs.html` (inline) **Key Features:** - Level-based icon and color theming (success=green, warning=yellow, error=red, critical=purple) - Stats cards grid with color-coded backgrounds - Table layout with icon-labeled rows - Conditional sections (exception, stack trace) with `x-show` - Copy-to-clipboard for stack traces - Dark mode support throughout ```jinja {# Example structure #}
{# Header with Icon and Badge #}

Title

Subtitle

Status
{# Body: Stats Cards + Details Table + Content Sections #}
{# Stats Cards Grid #}

150

Imported

{# Details Table #}
Field Name
Value
{# Footer #}
``` See the [Components Library](/admin/components#modals) for live examples. --- ## Pagination **File:** `shared/macros/pagination.html` ```jinja {% from 'shared/macros/pagination.html' import pagination, pagination_simple %} ``` ### pagination Full pagination with page numbers. ```jinja {{ pagination(show_condition='items.length > 0') }} ``` **Required Alpine.js properties:** - `pagination.page` - Current page - `pagination.total` - Total items - `startIndex` - First item index - `endIndex` - Last item index - `totalPages` - Total pages - `pageNumbers` - Array with page numbers and `'...'` **Required methods:** - `previousPage()` - `nextPage()` - `goToPage(pageNum)` ### pagination_simple Simple prev/next pagination. ```jinja {{ pagination_simple() }} ``` --- ## Tables **File:** `shared/macros/tables.html` ```jinja {% from 'shared/macros/tables.html' import table_wrapper, table_header, table_body, table_empty_state, table_cell_avatar, table_cell_date %} ``` ### table_wrapper Table container with overflow handling. ```jinja {% call table_wrapper() %} {{ table_header(['Name', 'Email', 'Status', 'Actions']) }} {% endcall %} ``` ### table_header Table header row. ```jinja {{ table_header(['Name', 'Email', 'Role', 'Status', 'Created', 'Actions']) }} ``` ### table_body Styled tbody wrapper. ```jinja {% call table_body() %} {% endcall %} ``` ### table_empty_state Empty state message. ```jinja {{ table_empty_state(colspan=6, icon='inbox', title='No users found', message='Create your first user to get started') }} ``` ### table_cell_avatar Cell with avatar and text. ```jinja {{ table_cell_avatar(image_src='item.avatar', title='item.name', subtitle='item.email') }} ``` ### table_cell_text Simple text cell. ```jinja {{ table_cell_text(text='item.email', is_dynamic=true, truncate=true) }} ``` ### table_cell_date Formatted date cell. ```jinja {{ table_cell_date(date_var='item.created_at') }} ``` ### table_loading_overlay Loading overlay for table. ```jinja {{ table_loading_overlay(show_condition='loading') }} ``` --- ## Complete Page Example ```jinja {% extends "admin/base.html" %} {% from 'shared/macros/headers.html' import page_header, breadcrumbs %} {% from 'shared/macros/cards.html' import stat_card, stats_grid, filter_card %} {% from 'shared/macros/forms.html' import search_input, filter_select %} {% from 'shared/macros/tables.html' import table_wrapper, table_header, table_body, table_empty_state, table_cell_avatar %} {% from 'shared/macros/badges.html' import status_badge, role_badge %} {% from 'shared/macros/buttons.html' import action_button %} {% from 'shared/macros/pagination.html' import pagination %} {% from 'shared/macros/modals.html' import confirm_modal %} {% from 'shared/macros/alerts.html' import loading_state, error_state %} {% block content %} {{ breadcrumbs([{'label': 'Admin', 'url': '/admin'}, {'label': 'Users'}]) }} {{ page_header('User Management', action_label='Add User', action_url='/admin/users/create') }} {# Stats #} {% call stats_grid() %} {{ stat_card('users', 'Total Users', 'stats.total', 'blue') }} {{ stat_card('check-circle', 'Active', 'stats.active', 'green') }} {{ stat_card('x-circle', 'Inactive', 'stats.inactive', 'red') }} {{ stat_card('shield-check', 'Admins', 'stats.admins', 'purple') }} {% endcall %} {# Filters #} {% call filter_card() %} {{ search_input(x_model='filters.search', on_input='debouncedSearch()') }} {{ filter_select(x_model='filters.status', options=[ {'value': 'active', 'label': 'Active'}, {'value': 'inactive', 'label': 'Inactive'} ], on_change='loadItems()') }} {% endcall %} {# Loading/Error States #} {{ loading_state() }} {{ error_state() }} {# Table #}
{% call table_wrapper() %} {{ table_header(['User', 'Role', 'Status', 'Created', 'Actions']) }} {% call table_body() %} {{ table_empty_state(5, show_condition='items.length === 0') }} {% endcall %} {% endcall %} {{ pagination() }}
{# Delete Confirmation Modal #} {{ confirm_modal('deleteModal', 'Delete User', 'Are you sure you want to delete this user?', 'deleteUser()', 'showDeleteModal') }} {% endblock %} ``` --- ## Related Documentation - [UI Components (HTML Reference)](ui-components.md) - [Pagination Guide](pagination.md) - [CDN Fallback Strategy](../cdn-fallback-strategy.md) - [Icons Guide](../../development/icons-guide.md)