refactor: migrate vendor APIs to token-based context and consolidate architecture
## Vendor-in-Token Architecture (Complete Migration) - Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id - Update permission dependencies to extract vendor from JWT token - Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException, InsufficientVendorPermissionsException - Shop endpoints retain require_vendor_context() for URL-based detection - Add AUTH-004 architecture rule enforcing vendor context patterns - Fix marketplace router missing /marketplace prefix ## Exception Pattern Fixes (API-003/API-004) - Services raise domain exceptions, endpoints let them bubble up - Add code_quality and content_page exception modules - Move business logic from endpoints to services (admin, auth, content_page) - Fix exception handling in admin, shop, and vendor endpoints ## Tailwind CSS Consolidation - Consolidate CSS to per-area files (admin, vendor, shop, platform) - Remove shared/cdn-fallback.html and shared/css/tailwind.min.css - Update all templates to use area-specific Tailwind output files - Remove Node.js config (package.json, postcss.config.js, tailwind.config.js) ## Documentation & Cleanup - Update vendor-in-token-architecture.md with completed migration status - Update architecture-rules.md with new rules - Move migration docs to docs/development/migration/ - Remove duplicate/obsolete documentation files - Merge pytest.ini settings into pyproject.toml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1585,7 +1585,7 @@ async applyFilters() {
|
||||
// marketplace.js
|
||||
return {
|
||||
...data(),
|
||||
currentPage: 'marketplace', // Highlights "Marketplace Import" in sidebar
|
||||
currentPage: 'marketplace', // Highlights "Marketplace" in sidebar
|
||||
// ...
|
||||
};
|
||||
|
||||
@@ -1597,6 +1597,28 @@ return {
|
||||
};
|
||||
```
|
||||
|
||||
### Collapsible Sections
|
||||
|
||||
Sidebar sections are collapsible with state persisted to localStorage:
|
||||
|
||||
```javascript
|
||||
// Section keys used in openSections state
|
||||
{
|
||||
platformAdmin: true, // Platform Administration (default open)
|
||||
contentMgmt: false, // Content Management
|
||||
devTools: false, // Developer Tools
|
||||
monitoring: false // Platform Monitoring
|
||||
}
|
||||
|
||||
// Toggle a section
|
||||
toggleSection('devTools');
|
||||
|
||||
// Check if section is open
|
||||
if (openSections.devTools) { ... }
|
||||
```
|
||||
|
||||
See [Sidebar Navigation](../shared/sidebar.md) for full documentation.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI Patterns
|
||||
|
||||
@@ -1,131 +1,158 @@
|
||||
# Complete Implementation Guide - Testing Hub, Components & Icons
|
||||
# Admin Sidebar Navigation
|
||||
|
||||
## 🎉 What's Been Created
|
||||
## Overview
|
||||
|
||||
### ✅ All Files Follow Your Alpine.js Architecture Perfectly!
|
||||
The admin sidebar provides navigation across all admin pages. It features collapsible sections with state persistence, active page indicators, and responsive mobile support.
|
||||
|
||||
1. **Testing Hub** - Manual QA tools
|
||||
2. **Components Library** - UI component reference with navigation
|
||||
3. **Icons Browser** - Searchable icon library with copy-to-clipboard
|
||||
4. **Sidebar Fix** - Active menu indicator for all pages
|
||||
**File Location:** `app/templates/admin/partials/sidebar.html`
|
||||
|
||||
---
|
||||
|
||||
## 📦 Files Created
|
||||
## Sidebar Structure
|
||||
|
||||
### JavaScript Files (Alpine.js Components)
|
||||
1. **[testing-hub.js](computer:///mnt/user-data/outputs/testing-hub.js)** - Testing hub component
|
||||
2. **[components.js](computer:///mnt/user-data/outputs/components.js)** - Components library component
|
||||
3. **[icons-page.js](computer:///mnt/user-data/outputs/icons-page.js)** - Icons browser component
|
||||
The sidebar is organized into the following sections:
|
||||
|
||||
### HTML Templates
|
||||
1. **[testing-hub.html](computer:///mnt/user-data/outputs/testing-hub.html)** - Testing hub page
|
||||
2. **[components.html](computer:///mnt/user-data/outputs/components.html)** - Components library page
|
||||
3. **[icons.html](computer:///mnt/user-data/outputs/icons.html)** - Icons browser page
|
||||
|
||||
### Sidebar Update
|
||||
1. **[sidebar-fixed.html](computer:///mnt/user-data/outputs/sidebar-fixed.html)** - Fixed sidebar with active indicators
|
||||
|
||||
### Documentation
|
||||
1. **[ARCHITECTURE_CONFIRMATION.md](computer:///mnt/user-data/outputs/ARCHITECTURE_CONFIRMATION.md)** - Architecture confirmation
|
||||
| Section | Collapsible | Pages |
|
||||
|---------|-------------|-------|
|
||||
| Dashboard | No | Dashboard |
|
||||
| Platform Administration | Yes | Companies, Vendors, Users, Customers, Marketplace |
|
||||
| Content Management | Yes | Platform Homepage, Content Pages, Vendor Themes |
|
||||
| Developer Tools | Yes | Components, Icons, Testing Hub, Code Quality |
|
||||
| Platform Monitoring | Yes | Import Jobs, Application Logs |
|
||||
| Settings | No | Settings |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Installation Steps
|
||||
## Collapsible Sections
|
||||
|
||||
### Step 1: Install JavaScript Files
|
||||
### How It Works
|
||||
|
||||
```bash
|
||||
# Copy to your static directory
|
||||
cp outputs/testing-hub.js static/admin/js/testing-hub.js
|
||||
cp outputs/components.js static/admin/js/components.js
|
||||
cp outputs/icons-page.js static/admin/js/icons-page.js
|
||||
Sections can be expanded/collapsed by clicking the section header. The state is persisted to `localStorage` so sections remain open/closed across page navigation and browser sessions.
|
||||
|
||||
### State Management
|
||||
|
||||
**File:** `static/admin/js/init-alpine.js`
|
||||
|
||||
```javascript
|
||||
// Default state: Platform Administration open, others closed
|
||||
const defaultSections = {
|
||||
platformAdmin: true,
|
||||
contentMgmt: false,
|
||||
devTools: false,
|
||||
monitoring: false
|
||||
};
|
||||
|
||||
// State stored in localStorage under this key
|
||||
const SIDEBAR_STORAGE_KEY = 'admin_sidebar_sections';
|
||||
```
|
||||
|
||||
### Step 2: Install HTML Templates
|
||||
### Available Methods
|
||||
|
||||
```bash
|
||||
# Copy to your templates directory
|
||||
cp outputs/testing-hub.html app/templates/admin/testing-hub.html
|
||||
cp outputs/components.html app/templates/admin/components.html
|
||||
cp outputs/icons.html app/templates/admin/icons.html
|
||||
```
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `toggleSection(section)` | Toggle a section open/closed |
|
||||
| `expandSectionForCurrentPage()` | Auto-expand section containing current page |
|
||||
| `openSections.platformAdmin` | Check if Platform Administration is open |
|
||||
| `openSections.contentMgmt` | Check if Content Management is open |
|
||||
| `openSections.devTools` | Check if Developer Tools is open |
|
||||
| `openSections.monitoring` | Check if Platform Monitoring is open |
|
||||
|
||||
### Step 3: Fix Sidebar (IMPORTANT!)
|
||||
### CSS Transitions
|
||||
|
||||
```bash
|
||||
# Replace your current sidebar
|
||||
cp outputs/sidebar-fixed.html app/templates/partials/sidebar.html
|
||||
```
|
||||
|
||||
### Step 4: Add Icons Route
|
||||
|
||||
Update `app/api/v1/admin/pages.py` - add this route:
|
||||
|
||||
```python
|
||||
@router.get("/icons", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_icons_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render icons browser page.
|
||||
Browse and search all available icons.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/icons.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Step 5: Verify Icons Are Updated
|
||||
|
||||
Make sure you're using the updated `icons-updated.js` from earlier:
|
||||
|
||||
```bash
|
||||
cp outputs/icons-updated.js static/shared/js/icons.js
|
||||
```
|
||||
|
||||
### Step 6: Restart Server
|
||||
|
||||
```bash
|
||||
# Stop current server (Ctrl+C)
|
||||
# Start again
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Sidebar Active Indicator Fix
|
||||
|
||||
### The Problem
|
||||
|
||||
You noticed that only the Dashboard menu item showed the vertical purple bar on the left when active. Other menu items didn't show this indicator.
|
||||
|
||||
### The Root Cause
|
||||
|
||||
Each page's JavaScript component needs to set `currentPage` correctly, and the sidebar HTML needs to check for that value.
|
||||
|
||||
**Before (Only Dashboard worked):**
|
||||
```html
|
||||
<!-- Only dashboard had the x-show condition -->
|
||||
<span x-show="currentPage === 'dashboard'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"></span>
|
||||
```
|
||||
|
||||
### The Fix
|
||||
|
||||
**1. Sidebar HTML** - Add the indicator `<span>` to EVERY menu item:
|
||||
Sections animate smoothly using CSS transitions (no plugins required):
|
||||
|
||||
```html
|
||||
<ul
|
||||
x-show="openSections.platformAdmin"
|
||||
x-transition:enter="transition-all duration-200 ease-out"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition-all duration-150 ease-in"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2"
|
||||
class="mt-1 overflow-hidden"
|
||||
>
|
||||
```
|
||||
|
||||
### Chevron Icon Rotation
|
||||
|
||||
The chevron icon rotates 180 degrees when a section is expanded:
|
||||
|
||||
```html
|
||||
<span
|
||||
x-html="$icon('chevron-down', 'w-4 h-4 transition-transform duration-200')"
|
||||
:class="{ 'rotate-180': openSections.platformAdmin }"
|
||||
></span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Page-to-Section Mapping
|
||||
|
||||
Pages are mapped to their parent sections for auto-expansion:
|
||||
|
||||
```javascript
|
||||
const pageSectionMap = {
|
||||
// Platform Administration
|
||||
companies: 'platformAdmin',
|
||||
vendors: 'platformAdmin',
|
||||
users: 'platformAdmin',
|
||||
customers: 'platformAdmin',
|
||||
marketplace: 'platformAdmin',
|
||||
// Content Management
|
||||
'platform-homepage': 'contentMgmt',
|
||||
'content-pages': 'contentMgmt',
|
||||
'vendor-theme': 'contentMgmt',
|
||||
// Developer Tools
|
||||
components: 'devTools',
|
||||
icons: 'devTools',
|
||||
testing: 'devTools',
|
||||
'code-quality': 'devTools',
|
||||
// Platform Monitoring
|
||||
imports: 'monitoring',
|
||||
logs: 'monitoring'
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Page Mapping
|
||||
|
||||
| Page | `currentPage` Value | Section | URL |
|
||||
|------|---------------------|---------|-----|
|
||||
| Dashboard | `'dashboard'` | (always visible) | `/admin/dashboard` |
|
||||
| Companies | `'companies'` | Platform Administration | `/admin/companies` |
|
||||
| Vendors | `'vendors'` | Platform Administration | `/admin/vendors` |
|
||||
| Users | `'users'` | Platform Administration | `/admin/users` |
|
||||
| Customers | `'customers'` | Platform Administration | `/admin/customers` |
|
||||
| Marketplace | `'marketplace'` | Platform Administration | `/admin/marketplace` |
|
||||
| Platform Homepage | `'platform-homepage'` | Content Management | `/admin/platform-homepage` |
|
||||
| Content Pages | `'content-pages'` | Content Management | `/admin/content-pages` |
|
||||
| Vendor Themes | `'vendor-theme'` | Content Management | `/admin/vendor-themes` |
|
||||
| Components | `'components'` | Developer Tools | `/admin/components` |
|
||||
| Icons | `'icons'` | Developer Tools | `/admin/icons` |
|
||||
| Testing Hub | `'testing'` | Developer Tools | `/admin/testing` |
|
||||
| Code Quality | `'code-quality'` | Developer Tools | `/admin/code-quality` |
|
||||
| Import Jobs | `'imports'` | Platform Monitoring | `/admin/imports` |
|
||||
| Application Logs | `'logs'` | Platform Monitoring | `/admin/logs` |
|
||||
| Settings | `'settings'` | (always visible) | `/admin/settings` |
|
||||
|
||||
---
|
||||
|
||||
## Active Page Indicator
|
||||
|
||||
Each menu item shows a purple vertical bar when active:
|
||||
|
||||
```html
|
||||
<!-- Vendors -->
|
||||
<li class="relative px-6 py-3">
|
||||
<!-- ✅ Add this span for the purple bar -->
|
||||
<span x-show="currentPage === 'vendors'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"></span>
|
||||
<a :class="currentPage === 'vendors' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
<!-- Purple bar indicator (shows when page is active) -->
|
||||
<span x-show="currentPage === 'vendors'"
|
||||
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
||||
aria-hidden="true"></span>
|
||||
|
||||
<!-- Link with active text styling -->
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'vendors' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/vendors">
|
||||
<span x-html="$icon('shopping-bag')"></span>
|
||||
<span class="ml-4">Vendors</span>
|
||||
@@ -133,329 +160,179 @@ Each page's JavaScript component needs to set `currentPage` correctly, and the s
|
||||
</li>
|
||||
```
|
||||
|
||||
**2. JavaScript Files** - Each component must set `currentPage`:
|
||||
### Setting currentPage in Components
|
||||
|
||||
Each page component must set `currentPage` to match the sidebar:
|
||||
|
||||
```javascript
|
||||
// vendors.js
|
||||
function adminVendors() {
|
||||
return {
|
||||
...data(),
|
||||
currentPage: 'vendors', // ✅ Must match sidebar check
|
||||
// ... rest of component
|
||||
};
|
||||
}
|
||||
|
||||
// users.js
|
||||
function adminUsers() {
|
||||
return {
|
||||
...data(),
|
||||
currentPage: 'users', // ✅ Must match sidebar check
|
||||
...data(), // Inherit base (includes openSections)
|
||||
currentPage: 'vendors', // Must match sidebar check
|
||||
// ... rest of component
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Page Mapping
|
||||
|
||||
| Page | JavaScript `currentPage` | Sidebar Check | URL |
|
||||
|------|--------------------------|---------------|-----|
|
||||
| Dashboard | `'dashboard'` | `x-show="currentPage === 'dashboard'"` | `/admin/dashboard` |
|
||||
| Companies | `'companies'` | `x-show="currentPage === 'companies'"` | `/admin/companies` |
|
||||
| Vendors | `'vendors'` | `x-show="currentPage === 'vendors'"` | `/admin/vendors` |
|
||||
| Users | `'users'` | `x-show="currentPage === 'users'"` | `/admin/users` |
|
||||
| Customers | `'customers'` | `x-show="currentPage === 'customers'"` | `/admin/customers` |
|
||||
| Marketplace | `'marketplace'` | `x-show="currentPage === 'marketplace'"` | `/admin/marketplace` |
|
||||
| Imports | `'imports'` | `x-show="currentPage === 'imports'"` | `/admin/imports` |
|
||||
| Components | `'components'` | `x-show="currentPage === 'components'"` | `/admin/components` |
|
||||
| Icons | `'icons'` | `x-show="currentPage === 'icons'"` | `/admin/icons` |
|
||||
| Testing | `'testing'` | `x-show="currentPage === 'testing'"` | `/admin/testing` |
|
||||
| Settings | `'settings'` | `x-show="currentPage === 'settings'"` | `/admin/settings` |
|
||||
|
||||
### Updated Sidebar Structure
|
||||
|
||||
The sidebar is organized into the following sections:
|
||||
|
||||
**Dashboard:**
|
||||
- Dashboard
|
||||
|
||||
**Platform Administration:**
|
||||
- Companies
|
||||
- Vendors
|
||||
- Users
|
||||
- Customers
|
||||
- Marketplace
|
||||
|
||||
**Content Management:**
|
||||
- Platform Homepage
|
||||
- Content Pages
|
||||
- Vendor Themes
|
||||
|
||||
**Developer Tools:**
|
||||
- Components
|
||||
- Icons
|
||||
- Testing Hub
|
||||
- Code Quality
|
||||
|
||||
**Platform Monitoring:**
|
||||
- Import Jobs
|
||||
- Application Logs
|
||||
|
||||
**Settings:**
|
||||
- Settings
|
||||
|
||||
Each section is properly separated with dividers and all menu items have active indicators.
|
||||
|
||||
---
|
||||
|
||||
## ✨ New Features
|
||||
## Jinja2 Macros
|
||||
|
||||
### Testing Hub
|
||||
- **2 Test Suites**: Auth Flow and Data Migration
|
||||
- **Stats Cards**: Quick metrics overview
|
||||
- **Interactive Cards**: Click to run tests
|
||||
- **Best Practices**: Testing guidelines
|
||||
- **Resource Links**: To Components and Icons pages
|
||||
The sidebar uses Jinja2 macros for DRY code:
|
||||
|
||||
### Components Library
|
||||
- **Sticky Section Navigation**: Jump to Forms, Buttons, Cards, etc.
|
||||
- **Hash-based URLs**: Bookmarkable sections (#forms, #buttons)
|
||||
- **Copy to Clipboard**: Click to copy component code
|
||||
- **Live Examples**: All components with real Alpine.js
|
||||
- **Dark Mode**: All examples support dark mode
|
||||
### section_header
|
||||
|
||||
### Icons Browser
|
||||
- **Search Functionality**: Filter icons by name
|
||||
- **Category Navigation**: Browse by category
|
||||
- **Live Preview**: See icons in multiple sizes
|
||||
- **Copy Icon Name**: Quick copy to clipboard
|
||||
- **Copy Usage Code**: Copy Alpine.js usage code
|
||||
- **Selected Icon Details**: Full preview and size examples
|
||||
- **Auto-categorization**: Icons organized automatically
|
||||
Creates a collapsible section header with chevron:
|
||||
|
||||
---
|
||||
|
||||
## 🎯 How Each Feature Works
|
||||
|
||||
### Components Library Navigation
|
||||
|
||||
1. **Click a section** in the left sidebar
|
||||
2. **Page scrolls** to that section smoothly
|
||||
3. **URL updates** with hash (#forms)
|
||||
4. **Active section** is highlighted in purple
|
||||
5. **Bookmarkable**: Share URL with #section
|
||||
|
||||
```javascript
|
||||
// How it works
|
||||
goToSection(sectionId) {
|
||||
this.activeSection = sectionId;
|
||||
window.location.hash = sectionId;
|
||||
// Smooth scroll
|
||||
document.getElementById(sectionId).scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
```jinja2
|
||||
{% macro section_header(title, section_key) %}
|
||||
<button
|
||||
@click="toggleSection('{{ section_key }}')"
|
||||
class="flex items-center justify-between w-full px-6 py-2 ..."
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
<span x-html="$icon('chevron-down', '...')"
|
||||
:class="{ 'rotate-180': openSections.{{ section_key }} }"></span>
|
||||
</button>
|
||||
{% endmacro %}
|
||||
```
|
||||
|
||||
### Icons Browser Search
|
||||
### section_content
|
||||
|
||||
1. **Type in search box** - filters as you type
|
||||
2. **Click category pill** - filters by category
|
||||
3. **Click any icon** - shows details panel
|
||||
4. **Hover icon** - shows copy buttons
|
||||
5. **Click copy** - copies to clipboard
|
||||
Wraps section items with collapse animation:
|
||||
|
||||
```javascript
|
||||
// How it works
|
||||
filterIcons() {
|
||||
let icons = this.allIcons;
|
||||
|
||||
// Filter by category
|
||||
if (this.activeCategory !== 'all') {
|
||||
icons = icons.filter(icon => icon.category === this.activeCategory);
|
||||
}
|
||||
|
||||
// Filter by search
|
||||
if (this.searchQuery.trim()) {
|
||||
const query = this.searchQuery.toLowerCase();
|
||||
icons = icons.filter(icon => icon.name.toLowerCase().includes(query));
|
||||
}
|
||||
|
||||
this.filteredIcons = icons;
|
||||
}
|
||||
```jinja2
|
||||
{% macro section_content(section_key) %}
|
||||
<ul x-show="openSections.{{ section_key }}" x-transition:...>
|
||||
{{ caller() }}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
```
|
||||
|
||||
### Testing Hub Navigation
|
||||
### menu_item
|
||||
|
||||
1. **View stats** at the top
|
||||
2. **Read test suite cards** with features
|
||||
3. **Click "Run Tests"** to go to test page
|
||||
4. **Read best practices** before testing
|
||||
Creates a menu item with active indicator:
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Checklist
|
||||
|
||||
After installation, verify:
|
||||
|
||||
### General
|
||||
- [ ] Server starts without errors
|
||||
- [ ] All routes load successfully
|
||||
- [ ] Icons display correctly everywhere
|
||||
- [ ] Dark mode works on all pages
|
||||
|
||||
### Sidebar
|
||||
- [ ] Dashboard shows purple bar when active
|
||||
- [ ] Vendors shows purple bar when active
|
||||
- [ ] Users shows purple bar when active
|
||||
- [ ] Components shows purple bar when active
|
||||
- [ ] Icons shows purple bar when active
|
||||
- [ ] Testing shows purple bar when active
|
||||
- [ ] Text is bold/highlighted on active page
|
||||
|
||||
### Testing Hub
|
||||
- [ ] Page loads at `/admin/testing`
|
||||
- [ ] Stats cards display correctly
|
||||
- [ ] Test suite cards are clickable
|
||||
- [ ] Icons render properly
|
||||
- [ ] Links to other pages work
|
||||
|
||||
### Components Library
|
||||
- [ ] Page loads at `/admin/components`
|
||||
- [ ] Section navigation works
|
||||
- [ ] Clicking section scrolls to it
|
||||
- [ ] URL hash updates (#forms, etc.)
|
||||
- [ ] Copy buttons work
|
||||
- [ ] All form examples render
|
||||
- [ ] Toast examples work
|
||||
|
||||
### Icons Browser
|
||||
- [ ] Page loads at `/admin/icons`
|
||||
- [ ] Shows correct icon count
|
||||
- [ ] Search filters icons
|
||||
- [ ] Category pills filter icons
|
||||
- [ ] Clicking icon shows details
|
||||
- [ ] Copy name button works
|
||||
- [ ] Copy usage button works
|
||||
- [ ] Preview shows multiple sizes
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Customization
|
||||
|
||||
### Adding More Test Suites
|
||||
|
||||
Edit `testing-hub.js`:
|
||||
|
||||
```javascript
|
||||
testSuites: [
|
||||
// ... existing suites
|
||||
{
|
||||
id: 'new-suite',
|
||||
name: 'New Test Suite',
|
||||
description: 'Description here',
|
||||
url: '/admin/test/new-suite',
|
||||
icon: 'icon-name',
|
||||
color: 'blue', // blue, orange, green, purple
|
||||
testCount: 5,
|
||||
features: [
|
||||
'Feature 1',
|
||||
'Feature 2'
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Adding More Component Sections
|
||||
|
||||
Edit `components.js`:
|
||||
|
||||
```javascript
|
||||
sections: [
|
||||
// ... existing sections
|
||||
{ id: 'new-section', name: 'New Section', icon: 'icon-name' }
|
||||
]
|
||||
```
|
||||
|
||||
Then add the section HTML in `components.html`:
|
||||
|
||||
```html
|
||||
<section id="new-section">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2>New Section</h2>
|
||||
<!-- Your components here -->
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
### Adding More Icon Categories
|
||||
|
||||
Edit `icons-page.js`:
|
||||
|
||||
```javascript
|
||||
categories: [
|
||||
// ... existing categories
|
||||
{ id: 'new-category', name: 'New Category', icon: 'icon-name' }
|
||||
]
|
||||
```
|
||||
|
||||
And update the `categorizeIcon()` function:
|
||||
|
||||
```javascript
|
||||
categoryMap: {
|
||||
// ... existing mappings
|
||||
'new-category': ['keyword1', 'keyword2']
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Quick Reference
|
||||
|
||||
### Alpine.js Component Pattern
|
||||
|
||||
```javascript
|
||||
function yourPageComponent() {
|
||||
return {
|
||||
...data(), // ✅ Inherit base
|
||||
currentPage: 'name', // ✅ Set page ID
|
||||
|
||||
async init() {
|
||||
if (window._yourPageInitialized) return;
|
||||
window._yourPageInitialized = true;
|
||||
// Your init code
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Sidebar Menu Item Pattern
|
||||
|
||||
```html
|
||||
```jinja2
|
||||
{% macro menu_item(page_id, url, icon, label) %}
|
||||
<li class="relative px-6 py-3">
|
||||
<!-- Active indicator -->
|
||||
<span x-show="currentPage === 'page-name'"
|
||||
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg">
|
||||
</span>
|
||||
|
||||
<!-- Link -->
|
||||
<a :class="currentPage === 'page-name' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/page-name">
|
||||
<span x-html="$icon('icon-name')"></span>
|
||||
<span class="ml-4">Page Name</span>
|
||||
<span x-show="currentPage === '{{ page_id }}'" class="..."></span>
|
||||
<a href="{{ url }}">
|
||||
<span x-html="$icon('{{ icon }}')"></span>
|
||||
<span class="ml-4">{{ label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endmacro %}
|
||||
```
|
||||
|
||||
### Usage Example
|
||||
|
||||
```jinja2
|
||||
{{ section_header('Platform Administration', 'platformAdmin') }}
|
||||
{% call section_content('platformAdmin') %}
|
||||
{{ menu_item('companies', '/admin/companies', 'office-building', 'Companies') }}
|
||||
{{ menu_item('vendors', '/admin/vendors', 'shopping-bag', 'Vendors') }}
|
||||
{% endcall %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Summary
|
||||
## Adding a New Page
|
||||
|
||||
**Architecture:** ✅ All files follow your Alpine.js patterns perfectly
|
||||
**Sidebar:** ✅ Fixed - all menu items now show active indicator
|
||||
**Testing Hub:** ✅ Complete with test suites and navigation
|
||||
**Components:** ✅ Complete with section navigation and copy feature
|
||||
**Icons:** ✅ Complete with search, categories, and copy features
|
||||
### Step 1: Add Route
|
||||
|
||||
**Total Files:** 7 (3 JS + 3 HTML + 1 Sidebar)
|
||||
**Lines of Code:** ~2000+
|
||||
**Features Added:** 20+
|
||||
Add the route in `app/routes/admin_pages.py`:
|
||||
|
||||
Everything is ready to install! 🚀
|
||||
```python
|
||||
@router.get("/new-page", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_new_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
):
|
||||
return templates.TemplateResponse(
|
||||
"admin/new-page.html",
|
||||
{"request": request, "user": current_user},
|
||||
)
|
||||
```
|
||||
|
||||
### Step 2: Create Template
|
||||
|
||||
Create `app/templates/admin/new-page.html`:
|
||||
|
||||
```jinja2
|
||||
{% extends "admin/base.html" %}
|
||||
{% block title %}New Page{% endblock %}
|
||||
{% block alpine_data %}adminNewPage(){% endblock %}
|
||||
{% block content %}
|
||||
<!-- Your content -->
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### Step 3: Create JavaScript Component
|
||||
|
||||
Create `static/admin/js/new-page.js`:
|
||||
|
||||
```javascript
|
||||
function adminNewPage() {
|
||||
return {
|
||||
...data(),
|
||||
currentPage: 'new-page', // Must match sidebar
|
||||
// ...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Add to Sidebar
|
||||
|
||||
Edit `app/templates/admin/partials/sidebar.html`:
|
||||
|
||||
```jinja2
|
||||
{# Add to appropriate section #}
|
||||
{{ menu_item('new-page', '/admin/new-page', 'icon-name', 'New Page') }}
|
||||
```
|
||||
|
||||
### Step 5: Update Page-Section Map (if in collapsible section)
|
||||
|
||||
Edit `static/admin/js/init-alpine.js`:
|
||||
|
||||
```javascript
|
||||
const pageSectionMap = {
|
||||
// ... existing mappings
|
||||
'new-page': 'platformAdmin', // Add mapping
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mobile Sidebar
|
||||
|
||||
The sidebar has a mobile version that slides in from the left:
|
||||
|
||||
- **Toggle:** Click hamburger menu in header
|
||||
- **Close:** Click outside, press Escape, or navigate
|
||||
- **State:** Controlled by `isSideMenuOpen`
|
||||
|
||||
```javascript
|
||||
// In base data()
|
||||
isSideMenuOpen: false,
|
||||
toggleSideMenu() {
|
||||
this.isSideMenuOpen = !this.isSideMenuOpen
|
||||
},
|
||||
closeSideMenu() {
|
||||
this.isSideMenuOpen = false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Sections expand/collapse on header click
|
||||
- [ ] Chevron rotates when section opens/closes
|
||||
- [ ] Section state persists after page reload
|
||||
- [ ] Section state persists across different pages
|
||||
- [ ] Active page shows purple bar indicator
|
||||
- [ ] Active page text is highlighted
|
||||
- [ ] Mobile sidebar opens/closes correctly
|
||||
- [ ] Collapsible sections work on mobile
|
||||
- [ ] All navigation links work correctly
|
||||
|
||||
@@ -1,144 +1,86 @@
|
||||
# UI Components Library Implementation Guide
|
||||
# UI Components Library
|
||||
|
||||
**Version:** 2.0
|
||||
**Last Updated:** December 2024
|
||||
**Live Reference:** `/admin/components`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
This guide covers the implementation of:
|
||||
1. **Components reference page** - A library showcasing all your UI components
|
||||
2. **Updated vendor edit page** - Using proper form components
|
||||
3. **Updated vendor detail page** - Using proper card components
|
||||
4. **Sidebar navigation** - Adding "Components" menu item
|
||||
|
||||
## Files Created
|
||||
The admin panel uses a consistent set of UI components built with Tailwind CSS and Alpine.js. All components support dark mode and are fully accessible.
|
||||
|
||||
### 1. Components Library Page
|
||||
**File:** `app/templates/admin/components.html`
|
||||
- Complete reference for all UI components
|
||||
- Quick navigation to sections (Forms, Buttons, Cards, etc.)
|
||||
- Copy-paste ready examples
|
||||
- Shows validation states, disabled states, helper text, etc.
|
||||
**Live Component Library:** Visit `/admin/components` in the admin panel to see all components with copy-paste ready code.
|
||||
|
||||
### 2. Updated Vendor Edit Page
|
||||
**File:** `app/templates/admin/vendor-edit-updated.html`
|
||||
- Uses proper form components from your library
|
||||
- Improved visual hierarchy with card sections
|
||||
- Better validation state displays (red borders for errors)
|
||||
- Quick actions section at the top
|
||||
- Status badges showing current state
|
||||
- Clean, consistent styling throughout
|
||||
---
|
||||
|
||||
### 3. Vendor Detail Page
|
||||
**File:** `app/templates/admin/vendor-detail.html`
|
||||
- NEW file (didn't exist before)
|
||||
- Uses card components to display vendor information
|
||||
- Status cards showing verification, active status, dates
|
||||
- Information organized in clear sections
|
||||
- All vendor data displayed in readable format
|
||||
- Delete action button
|
||||
## Page Layout Structure
|
||||
|
||||
### 4. JavaScript for Detail Page
|
||||
**File:** `static/admin/js/vendor-detail.js`
|
||||
- Loads vendor data
|
||||
- Handles delete action with double confirmation
|
||||
- Logging for debugging
|
||||
- Error handling
|
||||
All admin list pages follow a consistent structure:
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Add Components Menu to Sidebar
|
||||
|
||||
Update your `app/templates/admin/sidebar.html` (or wherever your sidebar is defined):
|
||||
|
||||
```html
|
||||
<!-- Add this menu item after "Settings" or wherever appropriate -->
|
||||
<li class="relative px-6 py-3">
|
||||
<a
|
||||
class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="{ 'text-gray-800 dark:text-gray-100': currentPage === 'components' }"
|
||||
href="/admin/components"
|
||||
>
|
||||
<span x-html="$icon('collection', 'w-5 h-5')"></span>
|
||||
<span class="ml-4">Components</span>
|
||||
</a>
|
||||
</li>
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Page Header (Title + Action Button) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Stats Cards (4 columns on desktop) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Search & Filters Bar │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Data Table │
|
||||
│ ├── Table Header │
|
||||
│ ├── Table Rows │
|
||||
│ └── Pagination │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Step 2: Add Components Page Route
|
||||
---
|
||||
|
||||
Update your `app/api/v1/admin/pages.py`:
|
||||
## Form Components
|
||||
|
||||
```python
|
||||
@router.get("/components", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_components_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render UI components reference page.
|
||||
Shows all available UI components for easy reference.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/components.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
```
|
||||
### Basic Input
|
||||
|
||||
### Step 3: Replace Vendor Edit Template
|
||||
|
||||
1. Backup your current: `app/templates/admin/vendor-edit.html`
|
||||
2. Replace it with: `vendor-edit-updated.html`
|
||||
3. Keep your existing `vendor-edit.js` (no changes needed)
|
||||
|
||||
### Step 4: Add Vendor Detail Template & JavaScript
|
||||
|
||||
1. Copy `vendor-detail.html` to `app/templates/admin/vendor-detail.html`
|
||||
2. Copy `vendor-detail.js` to `static/admin/js/vendor-detail.js`
|
||||
|
||||
## Component Usage Guide
|
||||
|
||||
### Form Components
|
||||
|
||||
#### Basic Input
|
||||
```html
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Label Text <span class="text-red-600">*</span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
x-model="formData.fieldName"
|
||||
required
|
||||
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
/>
|
||||
</label>
|
||||
```
|
||||
|
||||
#### Input with Validation Error
|
||||
```html
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Field Label</span>
|
||||
<input
|
||||
type="text"
|
||||
class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.fieldName }"
|
||||
x-model="formData.field"
|
||||
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
/>
|
||||
<span x-show="errors.fieldName" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.fieldName"></span>
|
||||
</label>
|
||||
```
|
||||
|
||||
#### Disabled Input
|
||||
### Required Field with Validation
|
||||
|
||||
```html
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Email Address <span class="text-red-600">*</span>
|
||||
</span>
|
||||
<input
|
||||
type="email"
|
||||
x-model="formData.email"
|
||||
required
|
||||
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.email }"
|
||||
class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
|
||||
/>
|
||||
<span x-show="errors.email" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.email"></span>
|
||||
</label>
|
||||
```
|
||||
|
||||
### Disabled/Read-Only Input
|
||||
|
||||
```html
|
||||
<input
|
||||
type="text"
|
||||
x-model="data.field"
|
||||
disabled
|
||||
value="Read-only value"
|
||||
class="block w-full mt-1 text-sm bg-gray-100 border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
```
|
||||
|
||||
#### Textarea
|
||||
### Textarea
|
||||
|
||||
```html
|
||||
<textarea
|
||||
x-model="formData.description"
|
||||
@@ -147,26 +89,90 @@ async def admin_components_page(
|
||||
></textarea>
|
||||
```
|
||||
|
||||
### Card Components
|
||||
### Select
|
||||
|
||||
```html
|
||||
<select
|
||||
x-model="formData.option"
|
||||
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-select"
|
||||
>
|
||||
<option value="">Select an option</option>
|
||||
<option value="option1">Option 1</option>
|
||||
<option value="option2">Option 2</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Button Components
|
||||
|
||||
### Primary Button
|
||||
|
||||
```html
|
||||
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
|
||||
Primary Button
|
||||
</button>
|
||||
```
|
||||
|
||||
### Button with Icon
|
||||
|
||||
```html
|
||||
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Add Item
|
||||
</button>
|
||||
```
|
||||
|
||||
### Secondary Button
|
||||
|
||||
```html
|
||||
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
|
||||
Cancel
|
||||
</button>
|
||||
```
|
||||
|
||||
### Danger Button
|
||||
|
||||
```html
|
||||
<button class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700">
|
||||
Delete
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Card Components
|
||||
|
||||
### Stats Card
|
||||
|
||||
#### Stats Card
|
||||
```html
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:text-orange-100 dark:bg-orange-500">
|
||||
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
|
||||
<span x-html="$icon('user-group', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Total Users
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
1,234
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total">
|
||||
0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Info Card
|
||||
### Stats Card Colors
|
||||
|
||||
| Color | Use Case | Classes |
|
||||
|-------|----------|---------|
|
||||
| Blue | Total counts | `text-blue-500 bg-blue-100` |
|
||||
| Green | Active/Success | `text-green-500 bg-green-100` |
|
||||
| Red | Inactive/Errors | `text-red-500 bg-red-100` |
|
||||
| Orange | Warnings/Admin | `text-orange-500 bg-orange-100` |
|
||||
| Purple | Primary metrics | `text-purple-500 bg-purple-100` |
|
||||
|
||||
### Info Card
|
||||
|
||||
```html
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
@@ -181,125 +187,281 @@ async def admin_components_page(
|
||||
</div>
|
||||
```
|
||||
|
||||
### Button Components
|
||||
---
|
||||
|
||||
## Status Badges
|
||||
|
||||
### Success Badge
|
||||
|
||||
#### Primary Button
|
||||
```html
|
||||
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
|
||||
Button Text
|
||||
</button>
|
||||
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-green-700 bg-green-100 rounded-full dark:bg-green-700 dark:text-green-100">
|
||||
<span x-html="$icon('check-circle', 'w-3 h-3 mr-1')"></span>
|
||||
Active
|
||||
</span>
|
||||
```
|
||||
|
||||
#### Button with Icon
|
||||
### Warning Badge
|
||||
|
||||
```html
|
||||
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Add Item
|
||||
</button>
|
||||
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-orange-700 bg-orange-100 rounded-full dark:bg-orange-700 dark:text-orange-100">
|
||||
<span x-html="$icon('clock', 'w-3 h-3 mr-1')"></span>
|
||||
Pending
|
||||
</span>
|
||||
```
|
||||
|
||||
#### Secondary Button
|
||||
### Danger Badge
|
||||
|
||||
```html
|
||||
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
|
||||
Cancel
|
||||
</button>
|
||||
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-red-700 bg-red-100 rounded-full dark:bg-red-700 dark:text-red-100">
|
||||
<span x-html="$icon('x-circle', 'w-3 h-3 mr-1')"></span>
|
||||
Inactive
|
||||
</span>
|
||||
```
|
||||
|
||||
## Features of Updated Pages
|
||||
### Dynamic Badge
|
||||
|
||||
### Vendor Edit Page Improvements
|
||||
```html
|
||||
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs"
|
||||
:class="item.is_active
|
||||
? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100'
|
||||
: 'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100'"
|
||||
x-text="item.is_active ? 'Active' : 'Inactive'">
|
||||
</span>
|
||||
```
|
||||
|
||||
1. **Quick Actions Section**
|
||||
- Verify/Unverify button
|
||||
- Activate/Deactivate button
|
||||
- Status badges showing current state
|
||||
|
||||
2. **Better Form Organization**
|
||||
- Clear sections with headers
|
||||
- Two-column layout on desktop
|
||||
- Helper text for all fields
|
||||
- Proper validation states
|
||||
|
||||
3. **Visual Consistency**
|
||||
- Uses standard form components
|
||||
- Consistent spacing and sizing
|
||||
- Dark mode support
|
||||
|
||||
4. **User Experience**
|
||||
- Disabled states for read-only fields
|
||||
- Clear indication of required fields
|
||||
- Loading states
|
||||
- Error messages inline with fields
|
||||
---
|
||||
|
||||
### Vendor Detail Page Features
|
||||
## Data Tables
|
||||
|
||||
1. **Status Overview**
|
||||
- 4 stats cards at top showing key metrics
|
||||
- Visual status indicators (colors, icons)
|
||||
|
||||
2. **Information Organization**
|
||||
- Basic info card
|
||||
- Contact info card
|
||||
- Business details section
|
||||
- Owner information section
|
||||
- Marketplace URLs (if available)
|
||||
|
||||
3. **Actions**
|
||||
- Edit button (goes to edit page)
|
||||
- Delete button (with double confirmation)
|
||||
- Back to list button
|
||||
### Basic Table Structure
|
||||
|
||||
## Quick Reference: Where to Find Components
|
||||
```html
|
||||
<div class="w-full overflow-hidden rounded-lg shadow-xs">
|
||||
<div class="w-full overflow-x-auto">
|
||||
<table class="w-full whitespace-no-wrap">
|
||||
<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">
|
||||
<th class="px-4 py-3">Name</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
<th class="px-4 py-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<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">
|
||||
<td class="px-4 py-3" x-text="item.name"></td>
|
||||
<td class="px-4 py-3"><!-- Status badge --></td>
|
||||
<td class="px-4 py-3"><!-- Action buttons --></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
When you need a component, visit `/admin/components` and you'll find:
|
||||
### Action Buttons
|
||||
|
||||
- **Forms Section**: All input types, validation states, helper text
|
||||
- **Buttons Section**: All button styles and states
|
||||
- **Cards Section**: Stats cards, info cards
|
||||
- **Tables Section**: (from your tables.html)
|
||||
- **Modals Section**: (from your modals.html)
|
||||
- **Charts Section**: (from your charts.html)
|
||||
```html
|
||||
<div class="flex items-center space-x-2 text-sm">
|
||||
<!-- View -->
|
||||
<a :href="'/admin/resource/' + item.id"
|
||||
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700"
|
||||
title="View">
|
||||
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
|
||||
## Testing Checklist
|
||||
<!-- Edit -->
|
||||
<a :href="'/admin/resource/' + item.id + '/edit'"
|
||||
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700"
|
||||
title="Edit">
|
||||
<span x-html="$icon('edit', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
|
||||
- [ ] `/admin/components` page loads and displays all components
|
||||
- [ ] Components menu item appears in sidebar
|
||||
- [ ] `/admin/vendors/{vendor_code}/edit` displays correctly
|
||||
- [ ] Form validation shows errors in red
|
||||
- [ ] Quick actions (verify/activate) work
|
||||
- [ ] `/admin/vendors/{vendor_code}` displays all vendor data
|
||||
- [ ] Status cards show correct information
|
||||
- [ ] Edit button navigates to edit page
|
||||
- [ ] Delete button shows double confirmation
|
||||
- [ ] All pages work in dark mode
|
||||
- [ ] All pages are responsive on mobile
|
||||
<!-- Delete -->
|
||||
<button @click="deleteItem(item)"
|
||||
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700"
|
||||
title="Delete">
|
||||
<span x-html="$icon('delete', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Color Scheme Reference
|
||||
---
|
||||
|
||||
Your component library uses these color schemes:
|
||||
## Loading & Error States
|
||||
|
||||
- **Primary**: Purple (`bg-purple-600`, `text-purple-600`)
|
||||
- **Success**: Green (`bg-green-600`, `text-green-600`)
|
||||
- **Warning**: Orange (`bg-orange-600`, `text-orange-600`)
|
||||
- **Danger**: Red (`bg-red-600`, `text-red-600`)
|
||||
- **Info**: Blue (`bg-blue-600`, `text-blue-600`)
|
||||
### Loading State
|
||||
|
||||
## Next Steps
|
||||
```html
|
||||
<div x-show="loading" class="text-center py-12">
|
||||
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
1. Implement the components page route
|
||||
2. Add menu item to sidebar
|
||||
3. Replace vendor-edit.html
|
||||
4. Add vendor-detail.html and .js
|
||||
5. Test all pages
|
||||
6. Apply same patterns to other admin pages (users, imports, etc.)
|
||||
### Error Alert
|
||||
|
||||
## Tips
|
||||
```html
|
||||
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
|
||||
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="font-semibold">Error loading data</p>
|
||||
<p class="text-sm" x-text="error"></p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- Always reference `/admin/components` when building new pages
|
||||
- Copy component HTML directly from the components page
|
||||
- Maintain consistent spacing and styling
|
||||
- Use Alpine.js x-model for form bindings
|
||||
- Use your icon system with `x-html="$icon('icon-name', 'w-5 h-5')"`
|
||||
- Test in both light and dark modes
|
||||
### Empty State
|
||||
|
||||
Enjoy your new component library! 🎨
|
||||
```html
|
||||
<div class="flex flex-col items-center py-12">
|
||||
<span x-html="$icon('inbox', 'w-12 h-12 text-gray-400 mb-4')"></span>
|
||||
<p class="text-lg font-medium text-gray-600 dark:text-gray-400">No items found</p>
|
||||
<p class="text-sm text-gray-500" x-text="filters.search ? 'Try adjusting your search' : 'Create your first item'"></p>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modal Components
|
||||
|
||||
### Confirmation Modal
|
||||
|
||||
```html
|
||||
<div
|
||||
x-show="showModal"
|
||||
x-cloak
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
|
||||
@click.self="showModal = false">
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 m-4 max-w-md w-full">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">
|
||||
Confirm Action
|
||||
</h3>
|
||||
<button @click="showModal = false" class="text-gray-400 hover:text-gray-600">
|
||||
<span x-html="$icon('x', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="mb-6 text-sm text-gray-600 dark:text-gray-400">
|
||||
Are you sure you want to perform this action?
|
||||
</p>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<button @click="showModal = false"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600">
|
||||
Cancel
|
||||
</button>
|
||||
<button @click="confirmAction()"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Toast Notifications
|
||||
|
||||
Use the global Utils helper:
|
||||
|
||||
```javascript
|
||||
Utils.showToast('Operation successful!', 'success');
|
||||
Utils.showToast('Something went wrong', 'error');
|
||||
Utils.showToast('Please check your input', 'warning');
|
||||
Utils.showToast('Here is some information', 'info');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Grid Layouts
|
||||
|
||||
### 2 Columns (Desktop)
|
||||
|
||||
```html
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<div>Column 1</div>
|
||||
<div>Column 2</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4 Columns (Stats Cards)
|
||||
|
||||
```html
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
|
||||
<!-- Stats cards here -->
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Reference
|
||||
|
||||
| Type | Primary | Success | Warning | Danger | Info |
|
||||
|------|---------|---------|---------|--------|------|
|
||||
| Background | `bg-purple-600` | `bg-green-600` | `bg-orange-600` | `bg-red-600` | `bg-blue-600` |
|
||||
| Text | `text-purple-600` | `text-green-600` | `text-orange-600` | `text-red-600` | `text-blue-600` |
|
||||
| Light BG | `bg-purple-100` | `bg-green-100` | `bg-orange-100` | `bg-red-100` | `bg-blue-100` |
|
||||
|
||||
---
|
||||
|
||||
## Common Icons
|
||||
|
||||
| Icon | Use Case |
|
||||
|------|----------|
|
||||
| `user-group` | Users/Teams |
|
||||
| `badge-check` | Verified |
|
||||
| `check-circle` | Success |
|
||||
| `x-circle` | Error/Inactive |
|
||||
| `clock` | Pending |
|
||||
| `calendar` | Dates |
|
||||
| `edit` | Edit |
|
||||
| `delete` | Delete |
|
||||
| `plus` | Add |
|
||||
| `arrow-left` | Back |
|
||||
| `exclamation` | Warning |
|
||||
| `spinner` | Loading |
|
||||
|
||||
---
|
||||
|
||||
## JavaScript Patterns
|
||||
|
||||
### List Page Structure
|
||||
|
||||
```javascript
|
||||
function adminResourceList() {
|
||||
return {
|
||||
...data(), // Inherit base layout
|
||||
currentPage: 'resource-name',
|
||||
|
||||
// State
|
||||
items: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
filters: { search: '', status: '' },
|
||||
stats: {},
|
||||
pagination: { page: 1, per_page: 10, total: 0 },
|
||||
|
||||
async init() {
|
||||
await this.loadItems();
|
||||
await this.loadStats();
|
||||
},
|
||||
|
||||
async loadItems() { /* ... */ },
|
||||
debouncedSearch() { /* ... */ },
|
||||
async deleteItem(item) { /* ... */ }
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Tailwind CSS](../tailwind-css.md)
|
||||
- [Tailwind CSS Official Docs](https://tailwindcss.com/docs)
|
||||
- [Alpine.js Official Docs](https://alpinejs.dev/)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tailwind CSS Build Guide
|
||||
|
||||
**Version:** 1.0
|
||||
**Version:** 2.0
|
||||
**Last Updated:** December 2024
|
||||
**Audience:** Frontend Developers
|
||||
|
||||
@@ -8,17 +8,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The platform uses [Tailwind CSS](https://tailwindcss.com/) for styling with a **dual-layer architecture**:
|
||||
|
||||
1. **Base Layer (CDN):** Tailwind CSS v2.2.19 loaded via CDN with local fallback
|
||||
2. **Override Layer (npm build):** Custom Windmill Dashboard theme built from Tailwind v1.4.6
|
||||
|
||||
This layered approach allows:
|
||||
- Fast loading via CDN for base utilities
|
||||
- Custom theme extensions (colors, dark mode, forms) via npm build
|
||||
- Offline support via local fallback
|
||||
|
||||
> **Note:** A migration to Tailwind v3.4 is planned. See [Migration Plan](tailwind-migration-plan.md).
|
||||
The platform uses [Tailwind CSS v4](https://tailwindcss.com/) with the **Standalone CLI** - no Node.js required. Each frontend (admin, vendor, shop, platform) has its own dedicated CSS configuration.
|
||||
|
||||
---
|
||||
|
||||
@@ -27,180 +17,230 @@ This layered approach allows:
|
||||
### How It Works
|
||||
|
||||
```
|
||||
Browser loads:
|
||||
1. CDN Tailwind 2.2.19 (base utilities)
|
||||
└── Fallback: static/shared/css/tailwind.min.css
|
||||
|
||||
2. Custom tailwind.output.css (overrides/extensions)
|
||||
└── Built from: static/admin/css/tailwind.css
|
||||
└── Contains: Windmill Dashboard theme, custom colors, dark mode
|
||||
Tailwind Standalone CLI (single binary, no npm)
|
||||
│
|
||||
├── static/admin/css/tailwind.css → tailwind.output.css (Admin)
|
||||
├── static/vendor/css/tailwind.css → tailwind.output.css (Vendor)
|
||||
├── static/shop/css/tailwind.css → tailwind.output.css (Shop)
|
||||
└── static/platform/css/tailwind.css → tailwind.output.css (Platform)
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Version | Purpose |
|
||||
|------|---------|---------|
|
||||
| CDN `tailwindcss@2.2.19` | 2.2.19 | Base Tailwind utilities |
|
||||
| `static/shared/css/tailwind.min.css` | 2.2.19 | Local fallback for CDN |
|
||||
| `tailwind.config.js` | 1.4.6 | Custom build configuration |
|
||||
| `static/admin/css/tailwind.css` | - | Source file with directives |
|
||||
| `static/admin/css/tailwind.output.css` | 1.4.6 | Compiled custom styles |
|
||||
| `static/vendor/css/tailwind.output.css` | 1.4.6 | Compiled custom styles |
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `~/.local/bin/tailwindcss` | Standalone CLI binary (v4.x) |
|
||||
| `static/*/css/tailwind.css` | CSS-first source configuration |
|
||||
| `static/*/css/tailwind.output.css` | Compiled output (do not edit) |
|
||||
|
||||
### Template Loading Order
|
||||
### Template Loading
|
||||
|
||||
Each frontend loads its own CSS:
|
||||
|
||||
```html
|
||||
<!-- 1. Base Tailwind from CDN (with fallback) -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
|
||||
onerror="this.onerror=null; this.href='/static/shared/css/tailwind.min.css';">
|
||||
<!-- Admin -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
|
||||
|
||||
<!-- 2. Custom overrides (built via npm) -->
|
||||
<link rel="stylesheet" href="/static/admin/css/tailwind.output.css" />
|
||||
<!-- Vendor -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='vendor/css/tailwind.output.css') }}" />
|
||||
|
||||
<!-- Shop -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='shop/css/tailwind.output.css') }}" />
|
||||
|
||||
<!-- Platform -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='platform/css/tailwind.output.css') }}" />
|
||||
```
|
||||
|
||||
See [CDN Fallback Strategy](cdn-fallback-strategy.md) for details on offline support.
|
||||
|
||||
---
|
||||
|
||||
## How Tailwind Purging Works
|
||||
## CSS-First Configuration (Tailwind v4)
|
||||
|
||||
Tailwind generates thousands of utility classes. To keep the CSS file small, it "purges" (removes) unused classes by scanning your source files.
|
||||
Tailwind v4 uses **CSS-first configuration** instead of `tailwind.config.js`. All customization happens directly in CSS files.
|
||||
|
||||
### Content Paths
|
||||
### Source File Structure
|
||||
|
||||
The `purge.content` array in `tailwind.config.js` tells Tailwind where to look for class usage:
|
||||
```css
|
||||
/* static/admin/css/tailwind.css */
|
||||
|
||||
```javascript
|
||||
purge: {
|
||||
content: [
|
||||
'public/**/*.html',
|
||||
'app/templates/**/*.html', // Jinja2 templates
|
||||
'static/**/*.js', // Alpine.js components
|
||||
],
|
||||
// ...
|
||||
/* Import Tailwind */
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Content sources for tree-shaking */
|
||||
@source "../../../app/templates/admin/**/*.html";
|
||||
@source "../../js/**/*.js";
|
||||
|
||||
/* Custom theme (colors, fonts, spacing) */
|
||||
@theme {
|
||||
--color-gray-50: #f9fafb;
|
||||
--color-gray-900: #121317;
|
||||
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* Dark mode variant */
|
||||
@variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/* Custom utilities */
|
||||
@layer utilities {
|
||||
.shadow-outline-purple {
|
||||
box-shadow: 0 0 0 3px hsla(262, 97%, 81%, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom components */
|
||||
@layer components {
|
||||
.form-input { ... }
|
||||
.btn-primary { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Safelist
|
||||
### Key Directives
|
||||
|
||||
Some classes are used dynamically in Alpine.js expressions and can't be detected by scanning. These must be added to the `safelist`:
|
||||
| Directive | Purpose | Example |
|
||||
|-----------|---------|---------|
|
||||
| `@import "tailwindcss"` | Import Tailwind base | Required at top |
|
||||
| `@source` | Content paths for purging | `@source "../../../app/templates/**/*.html"` |
|
||||
| `@theme` | Custom design tokens | `--color-purple-600: #7e3af2;` |
|
||||
| `@variant` | Custom variants | `@variant dark (&:where(.dark, .dark *))` |
|
||||
| `@layer utilities` | Custom utility classes | `.shadow-outline-*` |
|
||||
| `@layer components` | Custom components | `.form-input`, `.btn-*` |
|
||||
|
||||
```javascript
|
||||
purge: {
|
||||
content: [...],
|
||||
safelist: [
|
||||
'bg-orange-600',
|
||||
'bg-green-600',
|
||||
'bg-red-600',
|
||||
'hover:bg-orange-700',
|
||||
'hover:bg-green-700',
|
||||
'hover:bg-red-700',
|
||||
],
|
||||
---
|
||||
|
||||
## Custom Color Palette
|
||||
|
||||
All frontends share the same Windmill Dashboard color palette:
|
||||
|
||||
```css
|
||||
@theme {
|
||||
/* Gray (custom darker palette) */
|
||||
--color-gray-50: #f9fafb;
|
||||
--color-gray-100: #f4f5f7;
|
||||
--color-gray-200: #e5e7eb;
|
||||
--color-gray-300: #d5d6d7;
|
||||
--color-gray-400: #9e9e9e;
|
||||
--color-gray-500: #707275;
|
||||
--color-gray-600: #4c4f52;
|
||||
--color-gray-700: #24262d;
|
||||
--color-gray-800: #1a1c23;
|
||||
--color-gray-900: #121317;
|
||||
|
||||
/* Purple (primary) */
|
||||
--color-purple-600: #7e3af2;
|
||||
|
||||
/* Plus: red, orange, yellow, green, teal, blue, indigo, pink, cool-gray */
|
||||
}
|
||||
```
|
||||
|
||||
**When to add to safelist:**
|
||||
|
||||
- Classes used in Alpine.js `:class` bindings with dynamic conditions
|
||||
- Classes constructed from variables (e.g., `bg-${color}-500`)
|
||||
- Classes used only in JavaScript, not in HTML templates
|
||||
|
||||
---
|
||||
|
||||
## Building Tailwind CSS
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install Node.js dependencies (one-time setup):
|
||||
Install the standalone CLI (one-time setup):
|
||||
|
||||
```bash
|
||||
npm install
|
||||
make tailwind-install
|
||||
```
|
||||
|
||||
Or using Make:
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
make npm-install
|
||||
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
|
||||
chmod +x tailwindcss-linux-x64
|
||||
mv tailwindcss-linux-x64 ~/.local/bin/tailwindcss
|
||||
```
|
||||
|
||||
### Development Build
|
||||
|
||||
For development, build without purging (includes all classes, larger file):
|
||||
Build all frontends (includes all classes, larger files):
|
||||
|
||||
```bash
|
||||
# Build admin CSS
|
||||
npm run tailwind:admin
|
||||
|
||||
# Build vendor CSS
|
||||
npm run tailwind:vendor
|
||||
|
||||
# Or using Make
|
||||
make tailwind-dev
|
||||
```
|
||||
|
||||
### Production Build
|
||||
|
||||
For production, build with purging and minification (smaller file):
|
||||
Build with minification (smaller files):
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
|
||||
# Or using Make
|
||||
make tailwind-build
|
||||
```
|
||||
|
||||
---
|
||||
### Watch Mode
|
||||
|
||||
## Available npm Scripts
|
||||
Watch for changes during development:
|
||||
|
||||
| Script | Command | Description |
|
||||
|--------|---------|-------------|
|
||||
| `npm run tailwind:admin` | Build admin CSS (dev) | Fast, includes all classes |
|
||||
| `npm run tailwind:vendor` | Build vendor CSS (dev) | Fast, includes all classes |
|
||||
| `npm run build:admin` | Build admin CSS (prod) | Purged and minified |
|
||||
| `npm run build:vendor` | Build vendor CSS (prod) | Purged and minified |
|
||||
| `npm run build` | Build all (prod) | Runs both production builds |
|
||||
|
||||
---
|
||||
|
||||
## Adding New Utility Classes
|
||||
|
||||
If you need a class that doesn't exist in the compiled CSS:
|
||||
|
||||
### Option 1: Check if it's being purged
|
||||
|
||||
The class might exist but is being removed. Add it to the `safelist` in `tailwind.config.js`:
|
||||
|
||||
```javascript
|
||||
safelist: [
|
||||
'your-new-class',
|
||||
// ...
|
||||
]
|
||||
```bash
|
||||
make tailwind-watch
|
||||
```
|
||||
|
||||
### Option 2: Extend the theme
|
||||
---
|
||||
|
||||
Add custom values to `tailwind.config.js`:
|
||||
## Makefile Targets
|
||||
|
||||
| Target | Description |
|
||||
|--------|-------------|
|
||||
| `make tailwind-install` | Install Tailwind standalone CLI |
|
||||
| `make tailwind-dev` | Build all CSS (development) |
|
||||
| `make tailwind-build` | Build all CSS (production, minified) |
|
||||
| `make tailwind-watch` | Watch for changes |
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode
|
||||
|
||||
The platform uses class-based dark mode with the `.dark` class on the `<html>` element:
|
||||
|
||||
```html
|
||||
<html :class="{ 'dark': dark }">
|
||||
<body class="bg-white dark:bg-gray-900">
|
||||
```
|
||||
|
||||
Toggle dark mode with JavaScript:
|
||||
|
||||
```javascript
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'brand': '#123456',
|
||||
},
|
||||
spacing: {
|
||||
'128': '32rem',
|
||||
},
|
||||
},
|
||||
document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('darkMode', dark);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding Custom Utilities
|
||||
|
||||
Add custom utilities in the source CSS file:
|
||||
|
||||
```css
|
||||
@layer utilities {
|
||||
.shadow-outline-purple {
|
||||
box-shadow: 0 0 0 3px hsla(262, 97%, 81%, 0.45);
|
||||
}
|
||||
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 3: Rebuild CSS
|
||||
---
|
||||
|
||||
After any config change, rebuild the CSS:
|
||||
## Adding Custom Components
|
||||
|
||||
```bash
|
||||
make tailwind-dev
|
||||
Add reusable component classes:
|
||||
|
||||
```css
|
||||
@layer components {
|
||||
.form-input {
|
||||
@apply block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm;
|
||||
@apply focus:border-purple-500 focus:ring-1 focus:ring-purple-500;
|
||||
@apply dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg;
|
||||
@apply hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -209,105 +249,51 @@ make tailwind-dev
|
||||
|
||||
### Classes not appearing
|
||||
|
||||
1. **Check purge paths** - Ensure your file is in a scanned directory
|
||||
2. **Check safelist** - Dynamic classes need to be safelisted
|
||||
1. **Check @source paths** - Ensure your templates are in a scanned directory
|
||||
2. **Check class exists** - Tailwind v4 may have renamed some classes
|
||||
3. **Rebuild CSS** - Run `make tailwind-dev` after config changes
|
||||
|
||||
### Dynamic classes in Alpine.js
|
||||
|
||||
**Problem:** Classes in `:class` bindings may be purged.
|
||||
Tailwind v4 uses JIT compilation and scans for complete class names. Dynamic classes work automatically if the full class name appears in your templates:
|
||||
|
||||
```html
|
||||
<!-- This class might be purged -->
|
||||
<!-- This works - full class names visible -->
|
||||
<div :class="isActive ? 'bg-green-600' : 'bg-red-600'">
|
||||
```
|
||||
|
||||
**Solution:** Add to safelist:
|
||||
### Class name changes (v2 → v4)
|
||||
|
||||
```javascript
|
||||
safelist: ['bg-green-600', 'bg-red-600']
|
||||
```
|
||||
|
||||
### Large CSS file in development
|
||||
|
||||
Development builds include all Tailwind classes (~1.2MB). This is normal.
|
||||
|
||||
Production builds purge unused classes, resulting in much smaller files (~50-100KB typically).
|
||||
|
||||
---
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### tailwind.config.js Structure
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
// Where to scan for class usage
|
||||
purge: {
|
||||
content: [...],
|
||||
safelist: [...],
|
||||
},
|
||||
|
||||
// Theme customization
|
||||
theme: {
|
||||
// Override defaults
|
||||
colors: {...},
|
||||
|
||||
// Extend defaults
|
||||
extend: {
|
||||
fontFamily: {...},
|
||||
maxHeight: {...},
|
||||
},
|
||||
},
|
||||
|
||||
// Variant configuration (hover, focus, dark mode, etc.)
|
||||
variants: {
|
||||
backgroundColor: ['hover', 'focus', 'dark', 'dark:hover'],
|
||||
textColor: ['hover', 'dark'],
|
||||
// ...
|
||||
},
|
||||
|
||||
// Plugins
|
||||
plugins: [
|
||||
require('tailwindcss-multi-theme'),
|
||||
require('@tailwindcss/custom-forms'),
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Dark Mode
|
||||
|
||||
The platform uses `tailwindcss-multi-theme` for dark mode. Dark mode classes use the `.theme-dark` parent selector:
|
||||
|
||||
```css
|
||||
/* Light mode */
|
||||
.bg-white { ... }
|
||||
|
||||
/* Dark mode */
|
||||
.theme-dark .dark\:bg-gray-800 { ... }
|
||||
```
|
||||
| v2.x | v4.x |
|
||||
|------|------|
|
||||
| `overflow-ellipsis` | `text-ellipsis` |
|
||||
| `flex-grow-0` | `grow-0` |
|
||||
| `flex-shrink-0` | `shrink-0` |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always rebuild after config changes**
|
||||
1. **Always rebuild after CSS changes**
|
||||
```bash
|
||||
make tailwind-dev
|
||||
```
|
||||
|
||||
2. **Add dynamic classes to safelist** to prevent purging
|
||||
2. **Use production builds for deployment**
|
||||
```bash
|
||||
make tailwind-build
|
||||
```
|
||||
|
||||
3. **Use production builds for deployment** to minimize file size
|
||||
3. **Keep each frontend's CSS isolated** - Don't cross-import between frontends
|
||||
|
||||
4. **Check existing classes first** before adding custom ones - Tailwind likely has what you need
|
||||
4. **Use @apply sparingly** - Prefer utility classes in templates when possible
|
||||
|
||||
5. **Use consistent color scales** (e.g., `purple-600`, `purple-700`) for hover states
|
||||
5. **Test in both light and dark modes**
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Frontend Overview](overview.md)
|
||||
- [UI Components](shared/ui-components.md)
|
||||
- [Tailwind CSS Official Docs](https://tailwindcss.com/docs)
|
||||
- [Tailwind CSS v4 Blog Post](https://tailwindcss.com/blog/tailwindcss-v4)
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
# Tailwind CSS Migration Plan: v1.4/v2.2 → v3.4
|
||||
|
||||
**Created:** December 2024
|
||||
**Status:** Planned
|
||||
**Estimated Time:** 2-3 hours
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### Two Tailwind Setups
|
||||
|
||||
| Component | Version | Source | Purpose |
|
||||
|-----------|---------|--------|---------|
|
||||
| Base styles | 2.2.19 | CDN + local fallback | Core Tailwind utilities for all frontends |
|
||||
| Custom overrides | 1.4.6 | npm build | Windmill Dashboard theme (admin/vendor) |
|
||||
|
||||
### Current Files
|
||||
|
||||
```
|
||||
package.json # tailwindcss: 1.4.6
|
||||
tailwind.config.js # v1.4 format config
|
||||
static/shared/css/tailwind.min.css # CDN fallback (v2.2.19)
|
||||
static/admin/css/tailwind.output.css # Built overrides
|
||||
static/vendor/css/tailwind.output.css # Built overrides
|
||||
```
|
||||
|
||||
### Current Plugins
|
||||
|
||||
```json
|
||||
{
|
||||
"@tailwindcss/custom-forms": "0.2.1", // DEPRECATED in v3
|
||||
"tailwindcss-multi-theme": "1.0.3" // May not work with v3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Goals
|
||||
|
||||
1. Upgrade npm Tailwind to v3.4.x (latest stable)
|
||||
2. Upgrade CDN Tailwind to v3.4.x
|
||||
3. Update local fallback file
|
||||
4. Replace deprecated plugins
|
||||
5. Update config to v3 format
|
||||
6. Verify all frontends work correctly
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Migration
|
||||
|
||||
### Phase 1: Backup Current State
|
||||
|
||||
```bash
|
||||
# Create backup branch
|
||||
git checkout -b backup/tailwind-v1.4
|
||||
git add -A
|
||||
git commit -m "Backup before Tailwind v3 migration"
|
||||
git checkout master
|
||||
|
||||
# Create migration branch
|
||||
git checkout -b feat/tailwind-v3-upgrade
|
||||
```
|
||||
|
||||
### Phase 2: Update npm Dependencies
|
||||
|
||||
```bash
|
||||
# Remove old packages
|
||||
npm uninstall tailwindcss tailwindcss-multi-theme @tailwindcss/custom-forms
|
||||
|
||||
# Install new packages
|
||||
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
|
||||
npm install -D @tailwindcss/forms @tailwindcss/typography
|
||||
|
||||
# Verify versions
|
||||
npm list tailwindcss
|
||||
```
|
||||
|
||||
**Expected versions:**
|
||||
- tailwindcss: ^3.4.x
|
||||
- postcss: ^8.4.x
|
||||
- autoprefixer: ^10.4.x
|
||||
|
||||
### Phase 3: Update tailwind.config.js
|
||||
|
||||
Replace the entire config file:
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js - Tailwind CSS v3.4
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
// v3: 'content' replaces 'purge'
|
||||
content: [
|
||||
'app/templates/**/*.html',
|
||||
'static/**/*.js',
|
||||
],
|
||||
|
||||
// v3: safelist for dynamic classes
|
||||
safelist: [
|
||||
'bg-orange-600',
|
||||
'bg-green-600',
|
||||
'bg-red-600',
|
||||
'hover:bg-orange-700',
|
||||
'hover:bg-green-700',
|
||||
'hover:bg-red-700',
|
||||
// Add any other dynamic classes used in Alpine.js
|
||||
],
|
||||
|
||||
// v3: darkMode replaces tailwindcss-multi-theme
|
||||
darkMode: 'class', // or 'media' for OS preference
|
||||
|
||||
theme: {
|
||||
extend: {
|
||||
// Custom colors from Windmill Dashboard
|
||||
colors: {
|
||||
gray: {
|
||||
50: '#f9fafb',
|
||||
100: '#f4f5f7',
|
||||
200: '#e5e7eb',
|
||||
300: '#d5d6d7',
|
||||
400: '#9e9e9e',
|
||||
500: '#707275',
|
||||
600: '#4c4f52',
|
||||
700: '#24262d',
|
||||
800: '#1a1c23',
|
||||
900: '#121317',
|
||||
},
|
||||
purple: {
|
||||
50: '#f6f5ff',
|
||||
100: '#edebfe',
|
||||
200: '#dcd7fe',
|
||||
300: '#cabffd',
|
||||
400: '#ac94fa',
|
||||
500: '#9061f9',
|
||||
600: '#7e3af2',
|
||||
700: '#6c2bd9',
|
||||
800: '#5521b5',
|
||||
900: '#4a1d96',
|
||||
},
|
||||
// Add other custom colors as needed
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
},
|
||||
maxHeight: {
|
||||
'xl': '36rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'), // Replaces @tailwindcss/custom-forms
|
||||
require('@tailwindcss/typography'), // Optional: for prose content
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Update CSS Source File
|
||||
|
||||
Update `static/admin/css/tailwind.css`:
|
||||
|
||||
```css
|
||||
/* Tailwind CSS v3 directives */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Custom component classes if needed */
|
||||
@layer components {
|
||||
.form-input {
|
||||
@apply block w-full rounded-md border-gray-300 shadow-sm
|
||||
focus:border-purple-500 focus:ring-purple-500;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@apply block w-full rounded-md border-gray-300 shadow-sm
|
||||
focus:border-purple-500 focus:ring-purple-500;
|
||||
}
|
||||
|
||||
.form-checkbox {
|
||||
@apply rounded border-gray-300 text-purple-600
|
||||
focus:ring-purple-500;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Update postcss.config.js
|
||||
|
||||
```javascript
|
||||
// postcss.config.js
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 6: Update package.json Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"tailwind:admin": "npx tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css",
|
||||
"tailwind:vendor": "npx tailwindcss -i static/admin/css/tailwind.css -o static/vendor/css/tailwind.output.css",
|
||||
"tailwind:watch": "npx tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css --watch",
|
||||
"build:admin": "npx tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css --minify",
|
||||
"build:vendor": "npx tailwindcss -i static/admin/css/tailwind.css -o static/vendor/css/tailwind.output.css --minify",
|
||||
"build": "npm run build:admin && npm run build:vendor"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 7: Update Dark Mode Implementation
|
||||
|
||||
**Old approach (tailwindcss-multi-theme):**
|
||||
```html
|
||||
<body class="theme-dark">
|
||||
<div class="bg-white dark:bg-gray-800">
|
||||
```
|
||||
|
||||
**New approach (Tailwind v3 native):**
|
||||
```html
|
||||
<body class="dark">
|
||||
<div class="bg-white dark:bg-gray-800">
|
||||
```
|
||||
|
||||
**Files to update:**
|
||||
1. `app/templates/admin/base.html` - Change `theme-dark` to `dark`
|
||||
2. `app/templates/vendor/base.html` - Change `theme-dark` to `dark`
|
||||
3. `static/admin/js/init-alpine.js` - Update dark mode toggle logic
|
||||
4. `static/vendor/js/init-alpine.js` - Update dark mode toggle logic
|
||||
|
||||
**JavaScript update example:**
|
||||
```javascript
|
||||
// Old
|
||||
document.body.classList.toggle('theme-dark');
|
||||
|
||||
// New
|
||||
document.documentElement.classList.toggle('dark');
|
||||
```
|
||||
|
||||
### Phase 8: Update CDN and Local Fallback
|
||||
|
||||
**Option A: Use Tailwind Play CDN (development only)**
|
||||
```html
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
```
|
||||
|
||||
**Option B: Build and serve locally (recommended for production)**
|
||||
|
||||
Since Tailwind v3 doesn't have a pre-built CDN CSS file (it's JIT-only), the recommended approach is:
|
||||
|
||||
1. Remove CDN loading from templates
|
||||
2. Load only the built `tailwind.output.css`
|
||||
3. Include all needed utilities in your build
|
||||
|
||||
**Update base templates:**
|
||||
```html
|
||||
<!-- OLD: CDN with fallback -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
|
||||
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
|
||||
|
||||
<!-- NEW: Local only (v3) -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
|
||||
```
|
||||
|
||||
### Phase 9: Build and Test
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build CSS
|
||||
npm run tailwind:admin
|
||||
npm run tailwind:vendor
|
||||
|
||||
# Start development server
|
||||
make dev
|
||||
|
||||
# Test all frontends:
|
||||
# - http://localhost:8000/admin/dashboard
|
||||
# - http://localhost:8000/vendor/{code}/dashboard
|
||||
# - http://localhost:8000/ (shop)
|
||||
```
|
||||
|
||||
### Phase 10: Verify Checklist
|
||||
|
||||
- [ ] Admin dashboard loads correctly
|
||||
- [ ] Vendor dashboard loads correctly
|
||||
- [ ] Shop frontend loads correctly
|
||||
- [ ] Dark mode toggle works
|
||||
- [ ] All buttons have correct colors
|
||||
- [ ] Forms display correctly (inputs, selects, checkboxes)
|
||||
- [ ] Responsive design works (mobile/tablet/desktop)
|
||||
- [ ] No console errors
|
||||
- [ ] Production build works (`npm run build`)
|
||||
|
||||
---
|
||||
|
||||
## Breaking Changes Reference
|
||||
|
||||
### Class Name Changes (v2 → v3)
|
||||
|
||||
| v2.x | v3.x | Notes |
|
||||
|------|------|-------|
|
||||
| `overflow-ellipsis` | `text-ellipsis` | Renamed |
|
||||
| `flex-grow-0` | `grow-0` | Shortened |
|
||||
| `flex-shrink-0` | `shrink-0` | Shortened |
|
||||
| `decoration-clone` | `box-decoration-clone` | Renamed |
|
||||
|
||||
### Plugin Changes
|
||||
|
||||
| Old Plugin | New Plugin | Notes |
|
||||
|------------|------------|-------|
|
||||
| `@tailwindcss/custom-forms` | `@tailwindcss/forms` | Complete rewrite |
|
||||
| `tailwindcss-multi-theme` | Built-in `darkMode: 'class'` | Native support |
|
||||
|
||||
### Config Changes
|
||||
|
||||
| v1.x/v2.x | v3.x |
|
||||
|-----------|------|
|
||||
| `purge: [...]` | `content: [...]` |
|
||||
| `variants: {...}` | Removed (JIT generates all) |
|
||||
| `mode: 'jit'` | Default (not needed) |
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise:
|
||||
|
||||
```bash
|
||||
# Switch back to backup branch
|
||||
git checkout backup/tailwind-v1.4
|
||||
|
||||
# Or reset changes
|
||||
git checkout master
|
||||
git reset --hard HEAD~1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Post-Migration Tasks
|
||||
|
||||
1. Update `docs/frontend/tailwind-css.md` with v3 information
|
||||
2. Update `docs/frontend/cdn-fallback-strategy.md` (or remove CDN references)
|
||||
3. Remove `static/shared/css/tailwind.min.css` if no longer needed
|
||||
4. Update any documentation referencing old class names
|
||||
5. Consider adding Tailwind IntelliSense VS Code extension config
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Tailwind CSS v3 Upgrade Guide](https://tailwindcss.com/docs/upgrade-guide)
|
||||
- [Tailwind CSS v3 Documentation](https://tailwindcss.com/docs)
|
||||
- [@tailwindcss/forms Plugin](https://github.com/tailwindlabs/tailwindcss-forms)
|
||||
- [Dark Mode in Tailwind v3](https://tailwindcss.com/docs/dark-mode)
|
||||
@@ -1,387 +0,0 @@
|
||||
# UI Components
|
||||
|
||||
This document describes the reusable UI components and patterns used in the Wizamart admin panel.
|
||||
|
||||
## Page Layout Structure
|
||||
|
||||
All admin list pages follow a consistent structure:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Page Header (Title + Action Button) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Stats Cards (4 columns on desktop) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Search & Filters Bar │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Data Table │
|
||||
│ ├── Table Header │
|
||||
│ ├── Table Rows │
|
||||
│ └── Pagination │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Page Header
|
||||
|
||||
The page header contains the page title and primary action button.
|
||||
|
||||
```html
|
||||
<div class="flex items-center justify-between my-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||
Page Title
|
||||
</h2>
|
||||
<a href="/admin/resource/create"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Create Resource
|
||||
</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Stats Cards
|
||||
|
||||
Stats cards display key metrics in a 4-column grid layout.
|
||||
|
||||
### Structure
|
||||
|
||||
```html
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
|
||||
<!-- Card Template -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-{color}-500 bg-{color}-100 rounded-full dark:text-{color}-100 dark:bg-{color}-500">
|
||||
<span x-html="$icon('icon-name', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Label
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="value">
|
||||
0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Color Options
|
||||
|
||||
| Color | Use Case | Example |
|
||||
|-------|----------|---------|
|
||||
| `blue` | Total counts | Total Users, Total Companies |
|
||||
| `green` | Positive status | Active, Verified |
|
||||
| `red` | Negative status | Inactive, Errors |
|
||||
| `orange` | Special/Admin | Admin users, Warnings |
|
||||
| `purple` | Primary/Vendors | Active vendors, Main metrics |
|
||||
|
||||
### Icon Style
|
||||
|
||||
- Icons should be inside a **circular** container (`rounded-full`)
|
||||
- Icon size: `w-5 h-5`
|
||||
- Container padding: `p-3`
|
||||
|
||||
## Search & Filters Bar
|
||||
|
||||
The search and filters bar provides filtering capabilities for list pages.
|
||||
|
||||
### Structure
|
||||
|
||||
```html
|
||||
<div class="mb-6 px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<!-- Search Input -->
|
||||
<div class="flex-1 max-w-md">
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
x-model="filters.search"
|
||||
@input="debouncedSearch()"
|
||||
placeholder="Search..."
|
||||
class="w-full pl-10 pr-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:bg-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<span x-html="$icon('search', 'w-5 h-5 text-gray-400')"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Dropdowns -->
|
||||
<div class="flex gap-3">
|
||||
<select
|
||||
x-model="filters.filterName"
|
||||
@change="loadData()"
|
||||
class="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<option value="">All Options</option>
|
||||
<option value="value1">Option 1</option>
|
||||
<option value="value2">Option 2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### JavaScript Implementation
|
||||
|
||||
```javascript
|
||||
// State
|
||||
filters: {
|
||||
search: '',
|
||||
status: '',
|
||||
type: ''
|
||||
},
|
||||
|
||||
// Debounced search function
|
||||
debouncedSearch() {
|
||||
if (this._searchTimeout) {
|
||||
clearTimeout(this._searchTimeout);
|
||||
}
|
||||
this._searchTimeout = setTimeout(() => {
|
||||
this.pagination.page = 1; // Reset to first page
|
||||
this.loadData();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
// Load data with filters
|
||||
async loadData() {
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', this.pagination.page);
|
||||
params.append('per_page', this.pagination.per_page);
|
||||
|
||||
if (this.filters.search) {
|
||||
params.append('search', this.filters.search);
|
||||
}
|
||||
if (this.filters.status) {
|
||||
params.append('status', this.filters.status);
|
||||
}
|
||||
|
||||
const response = await apiClient.get(`/admin/resource?${params}`);
|
||||
// Handle response...
|
||||
}
|
||||
```
|
||||
|
||||
### Backend API Support
|
||||
|
||||
The API endpoint should support these query parameters:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `page` | int | Page number (1-based) |
|
||||
| `per_page` | int | Items per page |
|
||||
| `search` | string | Search term (searches multiple fields) |
|
||||
| `{filter}` | string | Filter by specific field |
|
||||
|
||||
Example API implementation:
|
||||
|
||||
```python
|
||||
@router.get("", response_model=ListResponse)
|
||||
def get_all(
|
||||
page: int = Query(1, ge=1),
|
||||
per_page: int = Query(10, ge=1, le=100),
|
||||
search: str = Query("", description="Search term"),
|
||||
status: str = Query("", description="Filter by status"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(Model)
|
||||
|
||||
# Apply search
|
||||
if search:
|
||||
search_term = f"%{search.lower()}%"
|
||||
query = query.filter(
|
||||
(Model.name.ilike(search_term)) |
|
||||
(Model.email.ilike(search_term))
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
if status:
|
||||
query = query.filter(Model.status == status)
|
||||
|
||||
# Pagination
|
||||
total = query.count()
|
||||
items = query.offset((page - 1) * per_page).limit(per_page).all()
|
||||
|
||||
return ListResponse(items=items, total=total, page=page, ...)
|
||||
```
|
||||
|
||||
## Data Table
|
||||
|
||||
### Structure
|
||||
|
||||
```html
|
||||
<div class="w-full overflow-hidden rounded-lg shadow-xs">
|
||||
<div class="w-full overflow-x-auto">
|
||||
<table class="w-full whitespace-no-wrap">
|
||||
<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">
|
||||
<th class="px-4 py-3">Column</th>
|
||||
<!-- More columns -->
|
||||
<th class="px-4 py-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<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">
|
||||
<!-- Columns -->
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
|
||||
<!-- Pagination controls -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Action Buttons
|
||||
|
||||
Standard action buttons for table rows:
|
||||
|
||||
```html
|
||||
<div class="flex items-center space-x-2 text-sm">
|
||||
<!-- View -->
|
||||
<a :href="'/admin/resource/' + item.id"
|
||||
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700"
|
||||
title="View">
|
||||
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
|
||||
<!-- Edit -->
|
||||
<a :href="'/admin/resource/' + item.id + '/edit'"
|
||||
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700"
|
||||
title="Edit">
|
||||
<span x-html="$icon('edit', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
|
||||
<!-- Delete -->
|
||||
<button @click="deleteItem(item)"
|
||||
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700"
|
||||
title="Delete">
|
||||
<span x-html="$icon('delete', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Status Badges
|
||||
|
||||
```html
|
||||
<!-- Active/Inactive -->
|
||||
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs"
|
||||
:class="item.is_active
|
||||
? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100'
|
||||
: 'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100'"
|
||||
x-text="item.is_active ? 'Active' : 'Inactive'">
|
||||
</span>
|
||||
|
||||
<!-- Role Badge -->
|
||||
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs capitalize"
|
||||
:class="{
|
||||
'text-orange-700 bg-orange-100 dark:bg-orange-700 dark:text-orange-100': item.role === 'admin',
|
||||
'text-purple-700 bg-purple-100 dark:bg-purple-700 dark:text-purple-100': item.role === 'vendor'
|
||||
}"
|
||||
x-text="item.role">
|
||||
</span>
|
||||
```
|
||||
|
||||
## Loading & Error States
|
||||
|
||||
### Loading State
|
||||
|
||||
```html
|
||||
<div x-show="loading" class="text-center py-12">
|
||||
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Error State
|
||||
|
||||
```html
|
||||
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
|
||||
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="font-semibold">Error loading data</p>
|
||||
<p class="text-sm" x-text="error"></p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Empty State
|
||||
|
||||
```html
|
||||
<template x-if="items.length === 0">
|
||||
<tr>
|
||||
<td colspan="7" class="px-4 py-8 text-center text-gray-600 dark:text-gray-400">
|
||||
<div class="flex flex-col items-center">
|
||||
<span x-html="$icon('inbox', 'w-12 h-12 text-gray-400 mb-4')"></span>
|
||||
<p class="text-lg font-medium">No items found</p>
|
||||
<p class="text-sm" x-text="filters.search ? 'Try adjusting your search' : 'Create your first item'"></p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Pages Using These Components
|
||||
|
||||
| Page | Stats | Search | Filters |
|
||||
|------|-------|--------|---------|
|
||||
| `/admin/users` | Total, Active, Inactive, Admins | Name, Email, Username | Role, Status |
|
||||
| `/admin/companies` | Total, Verified, Active, Vendors | Name, Email, Owner | Status |
|
||||
| `/admin/vendors` | Total, Verified, Active, Products | Name, Code, Subdomain | Status, Company |
|
||||
|
||||
## JavaScript Module Structure
|
||||
|
||||
Each list page follows this pattern:
|
||||
|
||||
```javascript
|
||||
function adminResourceList() {
|
||||
return {
|
||||
// Inherit base layout
|
||||
...data(),
|
||||
|
||||
// Page identifier (for sidebar highlighting)
|
||||
currentPage: 'resource-name',
|
||||
|
||||
// State
|
||||
items: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
filters: { search: '', status: '' },
|
||||
stats: {},
|
||||
pagination: { page: 1, per_page: 10, total: 0, pages: 0 },
|
||||
|
||||
// Initialize
|
||||
async init() {
|
||||
await this.loadItems();
|
||||
await this.loadStats();
|
||||
},
|
||||
|
||||
// Format helpers
|
||||
formatDate(dateString) {
|
||||
return dateString ? Utils.formatDate(dateString) : '-';
|
||||
},
|
||||
|
||||
// Data loading
|
||||
async loadItems() { /* ... */ },
|
||||
async loadStats() { /* ... */ },
|
||||
|
||||
// Search & filters
|
||||
debouncedSearch() { /* ... */ },
|
||||
|
||||
// Pagination
|
||||
nextPage() { /* ... */ },
|
||||
previousPage() { /* ... */ },
|
||||
|
||||
// Actions
|
||||
async deleteItem(item) { /* ... */ }
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Icons Reference](./icons.md)
|
||||
- [Alpine.js Integration](./alpine-integration.md)
|
||||
- [Tailwind CSS](./tailwind-css.md)
|
||||
Reference in New Issue
Block a user