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>
1135 lines
30 KiB
Markdown
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)
|