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:
2025-12-06 19:32:42 +01:00
parent c0e44c2751
commit fbd3a45c38
3 changed files with 457 additions and 0 deletions

View File

@@ -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
==================