From fbd3a45c387e7d754050b5b32b8f1c7ae09e9c3f Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sat, 6 Dec 2025 19:32:42 +0100 Subject: [PATCH] feat: complete TailAdmin parity with remaining macro features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/templates/shared/macros/cards.html | 163 +++++++++++++++++ app/templates/shared/macros/forms.html | 224 ++++++++++++++++++++++++ app/templates/shared/macros/tables.html | 70 ++++++++ 3 files changed, 457 insertions(+) diff --git a/app/templates/shared/macros/cards.html b/app/templates/shared/macros/cards.html index 758f0b90..a321b634 100644 --- a/app/templates/shared/macros/cards.html +++ b/app/templates/shared/macros/cards.html @@ -49,6 +49,65 @@ {% endmacro %} +{# + Stat Card with Trend + ==================== + A statistics card with icon, value, and trend indicator. + + Parameters: + - icon: Icon name + - label: Card label/title + - value: Alpine.js expression for the value + - trend_value: Alpine.js expression for trend percentage (e.g., '+12.5%' or '-3.2%') + - trend_direction: Alpine.js expression for direction ('up' | 'down' | 'neutral') + - color: Card color theme + - format: Optional format function + - compare_label: Label for comparison period (default: 'vs last month') +#} +{% macro stat_card_with_trend(icon, label, value, trend_value, trend_direction='neutral', color='orange', format=none, compare_label='vs last month') %} +{% set colors = { + 'orange': 'text-orange-500 bg-orange-100 dark:text-orange-100 dark:bg-orange-500', + 'green': 'text-green-500 bg-green-100 dark:text-green-100 dark:bg-green-500', + 'blue': 'text-blue-500 bg-blue-100 dark:text-blue-100 dark:bg-blue-500', + 'purple': 'text-purple-500 bg-purple-100 dark:text-purple-100 dark:bg-purple-500', + 'red': 'text-red-500 bg-red-100 dark:text-red-100 dark:bg-red-500', + 'yellow': 'text-yellow-500 bg-yellow-100 dark:text-yellow-100 dark:bg-yellow-500', + 'teal': 'text-teal-500 bg-teal-100 dark:text-teal-100 dark:bg-teal-500' +} %} +
+
+
+
+ +
+
+

{{ label }}

+

0

+
+
+
+ + + + + + + + + +

{{ compare_label }}

+
+
+
+{% endmacro %} + + {# Stats Grid ========== @@ -97,6 +156,110 @@ {% endmacro %} +{# + Card with Menu + ============== + A card with a dropdown menu in the header. + + Parameters: + - title: Card title + - subtitle: Card subtitle (optional) + - menu_var: Alpine.js variable for menu open state (default: 'menuOpen') + - class_extra: Additional CSS classes +#} +{% macro card_with_menu(title, subtitle=none, menu_var='menuOpen', class_extra='') %} +
+
+
+

{{ title }}

+ {% if subtitle %} +

{{ subtitle }}

+ {% endif %} +
+
+ +
+ {{ caller_menu() if caller_menu is defined else '' }} +
+
+
+
+ {{ caller() }} +
+
+{% endmacro %} + + +{# + Card with Menu (Simple) + ======================= + A simpler card with menu that accepts menu items as a parameter. + + Parameters: + - title: Card title + - menu_items: List of menu items [{'label': '', 'onclick': '', 'icon': '', 'variant': ''}] + - subtitle: Card subtitle (optional) + - class_extra: Additional CSS classes +#} +{% macro card_with_menu_simple(title, menu_items=[], subtitle=none, class_extra='') %} +
+
+
+

{{ title }}

+ {% if subtitle %} +

{{ subtitle }}

+ {% endif %} +
+ {% if menu_items %} +
+ +
+ {% for item in menu_items %} + + {% endfor %} +
+
+ {% endif %} +
+
+ {{ caller() }} +
+
+{% endmacro %} + + {# Info Card ========= diff --git a/app/templates/shared/macros/forms.html b/app/templates/shared/macros/forms.html index 2cbe6edf..564a536d 100644 --- a/app/templates/shared/macros/forms.html +++ b/app/templates/shared/macros/forms.html @@ -544,3 +544,227 @@ {% endmacro %} + + +{# + Searchable Select + ================= + A select dropdown with search/filter functionality. + + Parameters: + - label: Field label + - x_model: Alpine.js x-model binding for selected value + - options_var: Alpine.js variable containing options array + - value_key: Key for option value (default: 'value') + - label_key: Key for option label (default: 'label') + - search_var: Alpine.js variable for search query (default: 'searchQuery') + - open_var: Alpine.js variable for dropdown open state (default: 'isOpen') + - placeholder: Placeholder text (default: 'Select...') + - search_placeholder: Search input placeholder (default: 'Search...') + - required: Whether the field is required + - disabled: Alpine.js expression for disabled state + - no_results_text: Text shown when no results (default: 'No results found') + + Usage: +
+ {{ searchable_select('Category', 'selected', 'filteredOptions', value_key='id', label_key='name') }} +
+#} +{% macro searchable_select(label, x_model, options_var, value_key='value', label_key='label', search_var='searchQuery', open_var='isOpen', placeholder='Select...', search_placeholder='Search...', required=false, disabled=none, no_results_text='No results found') %} +
+ +
+{% endmacro %} + + +{# + Multi-Select with Tags + ====================== + A multi-select dropdown that shows selected items as tags. + + Parameters: + - label: Field label + - selected_var: Alpine.js variable for selected values array + - options_var: Alpine.js variable containing options array + - value_key: Key for option value (default: 'value') + - label_key: Key for option label (default: 'label') + - placeholder: Placeholder text (default: 'Select items...') + - max_items: Maximum number of items (optional) + + Required Alpine.js methods: + toggleOption(option) - Add/remove option from selection + removeOption(value) - Remove option by value + isSelected(value) - Check if option is selected + getLabel(value) - Get label for a value +#} +{% macro multi_select(label, selected_var, options_var, value_key='value', label_key='label', placeholder='Select items...', max_items=none) %} +
+ + {% if max_items %} +

+ / {{ max_items }} selected +

+ {% endif %} +
+{% endmacro %} diff --git a/app/templates/shared/macros/tables.html b/app/templates/shared/macros/tables.html index fdba39a6..0af93422 100644 --- a/app/templates/shared/macros/tables.html +++ b/app/templates/shared/macros/tables.html @@ -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') %} + + + {% for col in columns %} + + {% if col.sortable %} + + {% else %} + {{ col.label }} + {% endif %} + + {% endfor %} + + +{% endmacro %} + + {# Table Body Wrapper ==================