Files
orion/docs/frontend/shared/jinja-macros.md
Samir Boulahtit dad0989948 docs: add comprehensive Jinja macros documentation
New documentation:
- docs/frontend/shared/jinja-macros.md: Complete reference for all 94 macros
  - Alerts, Avatars, Badges, Buttons, Cards, Charts, Datepicker
  - Dropdowns, Forms, Headers, Modals, Pagination, Tables
  - Usage examples and parameter documentation
  - Complete page example showing all macros together

Updated documentation:
- docs/frontend/cdn-fallback-strategy.md:
  - Add Chart.js (v4.4.1) and Flatpickr (v4.6.13) sections
  - Document optional-libs.html loader macros
  - Update file structure and deployment checklist

- docs/frontend/shared/ui-components.md:
  - Add tip box recommending Jinja macros for new development
  - Update related documentation links

- mkdocs.yml:
  - Add Jinja Macros Library to navigation (top of Shared Components)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 19:15:27 +01:00

1135 lines
30 KiB
Markdown

# 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 | Modal dialogs, slide-overs |
| [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') %}
<template x-for="member in team.slice(0, 4)" :key="member.id">
{{ avatar_group_item(src='member.avatar', dynamic=true) }}
</template>
{% 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') %}
<p>Card content here</p>
{% 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() }}
<script>
document.addEventListener('DOMContentLoaded', function() {
createLineChart('salesChart',
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
[{
label: 'Sales',
data: [30, 40, 35, 50, 49, 60],
borderColor: chartColors.purple.solid,
backgroundColor: chartColors.purple.light
}]
);
});
</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') %}
<p>Modal content here</p>
{% 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') %}
<div class="space-y-4">
<p>Panel content here</p>
</div>
{% endcall %}
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `width` | string | `'md'` | `'sm'`, `'md'`, `'lg'`, `'xl'` |
---
## 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']) }}
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
<!-- rows -->
</tbody>
{% endcall %}
```
### table_header
Table header row.
```jinja
{{ table_header(['Name', 'Email', 'Role', 'Status', 'Created', 'Actions']) }}
```
### table_body
Styled tbody wrapper.
```jinja
{% call table_body() %}
<template x-for="item in items" :key="item.id">
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
<!-- cells -->
</tr>
</template>
{% 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 #}
<div x-show="!loading && !error">
{% call table_wrapper() %}
{{ table_header(['User', 'Role', 'Status', 'Created', 'Actions']) }}
{% call table_body() %}
{{ table_empty_state(5, show_condition='items.length === 0') }}
<template x-for="item in items" :key="item.id">
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
{{ table_cell_avatar(image_src='item.avatar', title='item.name', subtitle='item.email') }}
<td class="px-4 py-3">{{ role_badge(role='item.role') }}</td>
<td class="px-4 py-3">{{ status_badge(condition='item.is_active') }}</td>
<td class="px-4 py-3 text-sm" x-text="formatDate(item.created_at)"></td>
<td class="px-4 py-3">
<div class="flex items-center space-x-2">
{{ action_button(icon='eye', href="'/admin/users/' + item.id", variant='info') }}
{{ action_button(icon='edit', href="'/admin/users/' + item.id + '/edit'", variant='primary') }}
{{ action_button(icon='trash', onclick='confirmDelete(item)', variant='danger') }}
</div>
</td>
</tr>
</template>
{% endcall %}
{% endcall %}
{{ pagination() }}
</div>
{# 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)