feat: complete TailAdmin parity with remaining macro features
Add missing features identified in TailAdmin gap analysis: cards.html: - stat_card_with_trend: Stats card with up/down trend indicator and percentage - card_with_menu: Card with dropdown menu in header - card_with_menu_simple: Simplified version with menu_items parameter tables.html: - sortable_table_header: Table header with sortable columns, sort indicators, and Alpine.js integration for sort state management forms.html: - searchable_select: Dropdown with search/filter functionality - multi_select: Multi-select dropdown with tag display This completes feature parity with TailAdmin free template. The tailadmin-free-tailwind-dashboard-template folder can now be deleted. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,76 @@
|
||||
{% 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') %}
|
||||
<thead>
|
||||
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
|
||||
{% for col in columns %}
|
||||
<th class="px-4 py-3 {{ col.width if col.width else '' }}">
|
||||
{% if col.sortable %}
|
||||
<button
|
||||
@click="{{ on_sort }}('{{ col.key }}')"
|
||||
class="flex items-center space-x-1 hover:text-gray-700 dark:hover:text-gray-200 transition-colors group"
|
||||
>
|
||||
<span>{{ col.label }}</span>
|
||||
<span class="flex flex-col">
|
||||
<svg
|
||||
class="w-3 h-3 -mb-1 transition-colors"
|
||||
:class="{{ sort_key_var }} === '{{ col.key }}' && {{ sort_dir_var }} === 'asc' ? 'text-purple-600 dark:text-purple-400' : 'text-gray-300 dark:text-gray-600 group-hover:text-gray-400'"
|
||||
fill="currentColor" viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M5 12l5-5 5 5H5z"/>
|
||||
</svg>
|
||||
<svg
|
||||
class="w-3 h-3 -mt-1 transition-colors"
|
||||
:class="{{ sort_key_var }} === '{{ col.key }}' && {{ sort_dir_var }} === 'desc' ? 'text-purple-600 dark:text-purple-400' : 'text-gray-300 dark:text-gray-600 group-hover:text-gray-400'"
|
||||
fill="currentColor" viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M15 8l-5 5-5-5h10z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
{% else %}
|
||||
{{ col.label }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{#
|
||||
Table Body Wrapper
|
||||
==================
|
||||
|
||||
Reference in New Issue
Block a user