{# Table Macros ============ Reusable table components. Usage: {% from 'shared/macros/tables.html' import table_wrapper, table_header, table_empty_state %} {% call table_wrapper() %} {{ table_header(['Name', 'Email', 'Status', 'Actions']) }} ... {% endcall %} #} {# Table Wrapper ============= Wraps the table with proper overflow and shadow styling. #} {% macro table_wrapper(class_extra='') %}
{{ caller() }}
{% endmacro %} {# Table Header ============ Renders the table header row. Parameters: - columns: List of column names - sortable: Whether columns are sortable (default: false) - future enhancement #} {% macro table_header(columns) %} {% for column in columns %} {{ column }} {% endfor %} {% endmacro %} {# Custom Table Header =================== Renders a table header with custom content via caller(). Use this when you need th_sortable or custom th elements. Usage: {% call table_header_custom() %} {{ th_sortable('name', 'Name', 'sortBy', 'sortOrder') }} Actions {% endcall %} #} {% macro table_header_custom() %} {{ caller() }} {% endmacro %} {# Sortable Table Header Cell (th_sortable) ======================================== Renders a single sortable table header cell for use inside table_header_custom. Parameters: - key: The data key/column name for sorting - label: Display label for the column - sort_key_var: Alpine.js variable for current sort key (default: 'sortBy') - sort_order_var: Alpine.js variable for sort order (default: 'sortOrder') Usage: {% call table_header_custom() %} {{ th_sortable('name', 'Name', 'sortBy', 'sortOrder') }} {{ th_sortable('created_at', 'Created', 'sortBy', 'sortOrder') }} Actions {% endcall %} Required Alpine.js: sortBy: '', sortOrder: 'asc', handleSort(key) { if (this.sortBy === key) { this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'; } else { this.sortBy = key; this.sortOrder = 'asc'; } this.loadData(); // or loadSubscriptions(), etc. } #} {% macro th_sortable(key, label, sort_key_var='sortBy', sort_order_var='sortOrder') %} {% endmacro %} {# Sortable Table Header ===================== Renders a table header with sortable columns. Parameters: - columns: List of column definitions [{'label': '', 'key': '', 'sortable': true/false, 'width': ''}] - sort_key_var: Alpine.js variable for current sort key (default: 'sortKey') - sort_dir_var: Alpine.js variable for sort direction (default: 'sortDir') - on_sort: Alpine.js handler when column is clicked (default: 'sortBy') Usage: {{ sortable_table_header([ {'label': 'Name', 'key': 'name', 'sortable': true}, {'label': 'Email', 'key': 'email', 'sortable': true}, {'label': 'Status', 'key': 'status', 'sortable': false}, {'label': 'Actions', 'key': 'actions', 'sortable': false, 'width': 'w-24'} ]) }} Required Alpine.js: sortKey: 'name', sortDir: 'asc', sortBy(key) { if (this.sortKey === key) { this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc'; } else { this.sortKey = key; this.sortDir = 'asc'; } this.loadItems(); } #} {% macro sortable_table_header(columns, sort_key_var='sortKey', sort_dir_var='sortDir', on_sort='sortBy') %} {% for col in columns %} {% if col.sortable %} {% else %} {{ col.label }} {% endif %} {% endfor %} {% endmacro %} {# Table Body Wrapper ================== Wraps the tbody with proper styling. #} {% macro table_body() %} {{ caller() }} {% endmacro %} {# Table Empty State ================= Shows a centered message when the table has no data. Parameters: - colspan: Number of columns to span - icon: Icon name (default: 'inbox') - title: Empty state title - message: Empty state message - show_condition: Alpine.js condition (default: 'true') - has_filters: Whether to show filter hint (default: true) - x_message: Alpine.js expression for dynamic message (rendered via x-text) #} {% macro table_empty_state(colspan, icon='inbox', title='No data found', message='', show_condition='true', has_filters=true, x_message='') %} {% endmacro %} {# Table Row ========= A standard table row with hover styling. #} {% macro table_row(class_extra='') %} {{ caller() }} {% endmacro %} {# Table Cell ========== A standard table cell. #} {% macro table_cell(class_extra='') %} {{ caller() }} {% endmacro %} {# Table Cell with Text ==================== A simple text cell. Parameters: - text: Static text or Alpine.js expression for x-text - is_dynamic: Whether text is Alpine.js expression (default: false) - truncate: Whether to truncate with max-width (default: false) - max_width: Max width class (default: 'max-w-xs') #} {% macro table_cell_text(text, is_dynamic=false, truncate=false, max_width='max-w-xs') %} {% if truncate %}

{% if not is_dynamic %}{{ text }}{% endif %}

{% else %} {% if is_dynamic %} {% else %} {{ text }} {% endif %} {% endif %} {% endmacro %} {# Table Cell with Avatar ====================== A cell with avatar image and text. Parameters: - image_src: Image source (Alpine.js expression) - title: Primary text (Alpine.js expression) - subtitle: Secondary text (Alpine.js expression, optional) - fallback_icon: Icon to show if no image (default: 'user') #} {% macro table_cell_avatar(image_src, title, subtitle=none, fallback_icon='user') %}

{% if subtitle %}

{% endif %}
{% endmacro %} {# Table Cell with Date ==================== A cell that formats a date. Parameters: - date_var: Alpine.js variable containing the date - format_func: JavaScript date formatting function (default: 'formatDate') #} {% macro table_cell_date(date_var, format_func='formatDate') %} {% endmacro %} {# Table Loading Overlay ===================== An overlay shown while table data is loading. Parameters: - show_condition: Alpine.js condition (default: 'loading') - message: Loading message #} {% macro table_loading_overlay(show_condition='loading', message='Loading...') %}

{{ message }}

{% endmacro %} {# Numbered Pagination =================== A numbered pagination component with first/prev/pages/next/last buttons. Parameters: - page_var: Alpine.js variable for current page (default: 'page') - total_var: Alpine.js variable for total items (default: 'total') - limit_var: Alpine.js variable for items per page (default: 'limit') - on_change: Alpine.js function to call on page change (default: 'loadItems()') - show_rows_per_page: Whether to show rows per page selector (default: false) - rows_options: List of rows per page options (default: [10, 20, 50, 100]) Required Alpine.js: page: 1, total: 0, limit: 20, get totalPages() { return Math.ceil(this.total / this.limit); }, getPageNumbers() { // Returns array of page numbers to display const total = this.totalPages; const current = this.page; const maxVisible = 5; if (total <= maxVisible) { return Array.from({length: total}, (_, i) => i + 1); } const half = Math.floor(maxVisible / 2); let start = Math.max(1, current - half); let end = Math.min(total, start + maxVisible - 1); if (end - start < maxVisible - 1) { start = Math.max(1, end - maxVisible + 1); } return Array.from({length: end - start + 1}, (_, i) => start + i); }, goToPage(p) { this.page = p; this.loadItems(); } #} {% macro numbered_pagination(page_var='page', total_var='total', limit_var='limit', on_change='loadItems()', show_rows_per_page=false, rows_options=[10, 20, 50, 100]) %}
{# Info text #} Showing - of items {# Pagination controls #}
{# First page #} {# Previous page #} {# Page numbers #} {# Next page #} {# Last page #}
{% if show_rows_per_page %} {# Rows per page selector #}
{% endif %}
{% endmacro %} {# Simple Pagination ================= A simpler prev/next pagination without page numbers. Use this for quick implementation or when numbered pagination isn't needed. Parameters: - page_var: Alpine.js variable for current page (default: 'page') - total_var: Alpine.js variable for total items (default: 'total') - limit_var: Alpine.js variable for items per page (default: 'limit') - on_change: Alpine.js function to call on page change (default: 'loadItems()') #} {% macro simple_pagination(page_var='page', total_var='total', limit_var='limit', on_change='loadItems()') %}
Showing - of
{% endmacro %}