docs: add architecture rules and docs for e-commerce components

Architecture rules added:
- FE-008: Use number_stepper macro for quantity inputs
- FE-009: Use product_card macro for product displays
- FE-010: Use product_grid macro for product listings
- FE-011: Use add_to_cart macros for cart interactions
- FE-012: Use mini_cart macro for cart dropdown

Documentation:
- Update ui-components-quick-reference.md with e-commerce section
- Add component-standards.md for standardization guidelines
- Add ecommerce-components-proposal.md with full 20-component roadmap
- Update validate_architecture.py with FE-008 detection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-07 17:04:28 +01:00
parent c903248846
commit 95a8ffc645
7 changed files with 1629 additions and 9 deletions

View File

@@ -0,0 +1,461 @@
# Frontend Component Standards
**Version:** 1.0
**Last Updated:** December 2025
**Audience:** Frontend Developers
---
## Overview
This document defines mandatory standards for all frontend components across Admin, Vendor, and Shop frontends. Following these standards ensures consistency, maintainability, and a unified user experience.
---
## Golden Rules
1. **Use Jinja Macros** - Never copy-paste HTML patterns; use shared macros
2. **Alpine.js for Interactivity** - All client-side logic uses Alpine.js
3. **Dark Mode Required** - Every component must support dark mode
4. **Accessibility First** - ARIA labels, keyboard navigation, focus states
5. **Mobile Responsive** - Mobile-first design with Tailwind breakpoints
---
## Jinja Macro System
### Available Macro Files
All macros are located in `app/templates/shared/macros/`:
| File | Purpose | Key Macros |
|------|---------|------------|
| `pagination.html` | Table pagination | `pagination()`, `pagination_simple()` |
| `alerts.html` | Alerts and toasts | `loading_state()`, `error_state()`, `alert_dynamic()` |
| `badges.html` | Status indicators | `badge()`, `status_badge()`, `role_badge()` |
| `buttons.html` | Button variants | `btn_primary()`, `btn_secondary()`, `action_button()` |
| `forms.html` | Form inputs | `form_input()`, `form_select()`, `form_textarea()` |
| `tables.html` | Table components | `table_wrapper()`, `table_header()`, `table_empty_state()` |
| `cards.html` | Card layouts | `stat_card()`, `card()`, `info_card()` |
| `headers.html` | Page headers | `page_header()`, `page_header_flex()`, `refresh_button()` |
| `modals.html` | Modal dialogs | `modal()`, `confirm_modal()`, `job_details_modal()` |
| `tabs.html` | Tab navigation | `tabs_nav()`, `tabs_inline()`, `tab_button()` |
| `inputs.html` | Specialized inputs | `search_autocomplete()`, `number_stepper()` |
### Macro Usage Rules
#### RULE 1: Always Import Before Use
```jinja
{# ✅ CORRECT - Import at top of template #}
{% from 'shared/macros/tables.html' import table_wrapper, table_header %}
{% from 'shared/macros/pagination.html' import pagination %}
{% block content %}
{% call table_wrapper() %}
{{ table_header(['Name', 'Email', 'Status']) }}
...
{% endcall %}
{{ pagination() }}
{% endblock %}
{# ❌ WRONG - Inline HTML instead of macro #}
{% block content %}
<div class="w-full overflow-hidden rounded-lg shadow-xs">
<div class="w-full overflow-x-auto">
<table class="w-full whitespace-no-wrap">
...
</table>
</div>
</div>
{% endblock %}
```
#### RULE 2: Use Macros for Repeated Patterns
If you find yourself copying HTML more than once, create or use a macro.
```jinja
{# ✅ CORRECT - Using badge macro #}
{{ status_badge(status='active') }}
{{ status_badge(status='pending') }}
{{ status_badge(status='inactive') }}
{# ❌ WRONG - Copy-pasting badge HTML #}
<span class="px-2 py-1 text-xs font-semibold text-green-800 bg-green-100 rounded-full">Active</span>
<span class="px-2 py-1 text-xs font-semibold text-yellow-800 bg-yellow-100 rounded-full">Pending</span>
```
#### RULE 3: Use `{% call %}` for Wrapper Macros
Macros that wrap content use the `caller()` pattern:
```jinja
{# ✅ CORRECT - Using call block #}
{% call table_wrapper() %}
{{ table_header(['Column 1', 'Column 2']) }}
<tbody>...</tbody>
{% endcall %}
{% call tabs_nav() %}
{{ tab_button('tab1', 'First Tab', icon='home') }}
{{ tab_button('tab2', 'Second Tab', icon='cog') }}
{% endcall %}
```
#### RULE 4: Prefer Macro Parameters Over Custom CSS
```jinja
{# ✅ CORRECT - Using size parameter #}
{{ number_stepper(model='qty', size='sm') }}
{{ number_stepper(model='qty', size='lg') }}
{# ❌ WRONG - Adding custom classes #}
{{ number_stepper(model='qty') }}
<style>.my-stepper { transform: scale(0.8); }</style>
```
---
## Component Categories
### 1. Layout Components
#### Page Headers
```jinja
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}
{% call page_header_flex(title='Page Title', subtitle='Optional description') %}
{{ refresh_button(onclick='refreshData()') }}
<button class="...">Add New</button>
{% endcall %}
```
#### Cards
```jinja
{% from 'shared/macros/cards.html' import stat_card %}
{{ stat_card(
label='Total Users',
value='1,234',
icon='user-group',
color='purple'
) }}
```
### 2. Data Display Components
#### Tables with Pagination
```jinja
{% from 'shared/macros/tables.html' import table_wrapper, table_header, table_empty_state %}
{% from 'shared/macros/pagination.html' import pagination %}
{% call table_wrapper() %}
{{ table_header(['Name', 'Email', 'Status', 'Actions']) }}
<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">
...
</tr>
</template>
</tbody>
{% endcall %}
{{ table_empty_state(
message='No items found',
icon='inbox',
show_condition='!loading && items.length === 0'
) }}
{{ pagination() }}
```
#### Badges
```jinja
{% from 'shared/macros/badges.html' import status_badge %}
{{ status_badge(status='active') }} {# Green #}
{{ status_badge(status='pending') }} {# Yellow #}
{{ status_badge(status='inactive') }} {# Red #}
{{ status_badge(status='processing') }} {# Blue #}
```
### 3. Form Components
#### Standard Inputs
```jinja
{% from 'shared/macros/forms.html' import form_input, form_select %}
{{ form_input(
name='email',
label='Email Address',
type='email',
model='formData.email',
required=true,
error_var='errors.email'
) }}
{{ form_select(
name='status',
label='Status',
model='formData.status',
options=[
{'value': 'active', 'label': 'Active'},
{'value': 'inactive', 'label': 'Inactive'}
]
) }}
```
#### Number Stepper
```jinja
{% from 'shared/macros/inputs.html' import number_stepper %}
{# Cart quantity #}
{{ number_stepper(model='quantity', min=1, max=99, size='md') }}
{# Batch size with step #}
{{ number_stepper(model='batchSize', min=100, max=5000, step=100, size='lg') }}
```
#### Search Autocomplete
```jinja
{% from 'shared/macros/inputs.html' import search_autocomplete, selected_item_display %}
{{ search_autocomplete(
search_var='searchQuery',
results_var='searchResults',
show_dropdown_var='showDropdown',
loading_var='searching',
select_action='selectItem(item)',
display_field='name',
secondary_field='email',
placeholder='Search users...'
) }}
{{ selected_item_display(
selected_var='selectedUser',
display_field='name',
secondary_field='email',
clear_action='clearSelection()'
) }}
```
### 4. Navigation Components
#### Tabs
```jinja
{% from 'shared/macros/tabs.html' import tabs_nav, tabs_inline, tab_button %}
{# Full-width navigation tabs #}
{% call tabs_nav() %}
{{ tab_button('overview', 'Overview', icon='home') }}
{{ tab_button('details', 'Details', icon='document') }}
{{ tab_button('settings', 'Settings', icon='cog') }}
{% endcall %}
{# Inline tabs with counts #}
{% call tabs_inline() %}
{{ tab_button('all', 'All', count_var='items.length') }}
{{ tab_button('active', 'Active', count_var='activeCount') }}
{{ tab_button('archived', 'Archived', count_var='archivedCount') }}
{% endcall %}
```
### 5. Feedback Components
#### Alerts
```jinja
{% from 'shared/macros/alerts.html' import alert_dynamic, loading_state, error_state %}
{{ alert_dynamic(type='success', message_var='successMessage', show_condition='successMessage') }}
{{ alert_dynamic(type='error', message_var='errorMessage', show_condition='errorMessage') }}
{{ loading_state(message='Loading data...', show_condition='loading') }}
{{ error_state(title='Error', show_condition='error') }}
```
#### Modals
```jinja
{% from 'shared/macros/modals.html' import confirm_modal %}
{{ confirm_modal(
show_var='showDeleteModal',
title='Confirm Delete',
message='Are you sure you want to delete this item?',
confirm_action='deleteItem()',
confirm_text='Delete',
confirm_class='bg-red-600 hover:bg-red-700'
) }}
```
---
## Dark Mode Requirements
Every component MUST support dark mode using Tailwind's `dark:` prefix.
### Color Pairing Reference
| Element | Light Mode | Dark Mode |
|---------|------------|-----------|
| Background | `bg-white` | `dark:bg-gray-800` |
| Card Background | `bg-gray-50` | `dark:bg-gray-900` |
| Text Primary | `text-gray-700` | `dark:text-gray-200` |
| Text Secondary | `text-gray-500` | `dark:text-gray-400` |
| Border | `border-gray-300` | `dark:border-gray-600` |
| Hover Background | `hover:bg-gray-100` | `dark:hover:bg-gray-700` |
| Input Background | `bg-white` | `dark:bg-gray-700` |
| Focus Ring | `focus:ring-purple-500` | `dark:focus:ring-purple-400` |
### Example
```html
<div class="bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 border border-gray-300 dark:border-gray-600">
<h3 class="text-gray-900 dark:text-gray-100">Title</h3>
<p class="text-gray-500 dark:text-gray-400">Description</p>
</div>
```
---
## Accessibility Requirements
### Required ARIA Attributes
```html
{# Button with loading state #}
<button :aria-busy="loading" :disabled="loading">
<span x-show="!loading">Submit</span>
<span x-show="loading">Loading...</span>
</button>
{# Icon-only button #}
<button aria-label="Delete item" title="Delete">
<span x-html="$icon('trash', 'w-4 h-4')"></span>
</button>
{# Number stepper group #}
<div role="group" aria-label="Quantity selector">
<button aria-label="Decrease quantity">-</button>
<input aria-label="Quantity" />
<button aria-label="Increase quantity">+</button>
</div>
```
### Focus States
All interactive elements must have visible focus states:
```html
<button class="focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2">
Click me
</button>
```
---
## Icon System
### Usage
```html
{# Standard icon #}
<span x-html="$icon('user', 'w-5 h-5')"></span>
{# Icon with color #}
<span x-html="$icon('check-circle', 'w-5 h-5 text-green-500')"></span>
{# Inline icon in button #}
<button class="flex items-center">
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Add Item
</button>
```
### Common Icons Reference
| Icon | Usage |
|------|-------|
| `home` | Dashboard/Home |
| `user` | Users/Profile |
| `cog` | Settings |
| `plus` | Add/Create |
| `edit` | Edit/Modify |
| `trash` | Delete |
| `check-circle` | Success/Verified |
| `x-circle` | Error/Failed |
| `clock` | Pending/Time |
| `refresh` | Reload/Sync |
| `eye` | View/Details |
| `download` | Download/Export |
| `upload` | Upload/Import |
| `search` | Search |
| `filter` | Filter |
| `minus` | Decrease |
---
## Creating New Components
### When to Create a Macro
Create a new macro when:
- Pattern is used 3+ times across templates
- Component has configurable options
- Component requires consistent styling
### Macro Template
```jinja
{#
Component Name
==============
Brief description of what the component does.
Parameters:
- param1: Description (required/optional, default: value)
- param2: Description
Usage:
{{ component_name(param1='value', param2='value') }}
#}
{% macro component_name(
param1,
param2='default'
) %}
<div class="...">
{# Component HTML #}
</div>
{% endmacro %}
```
### Checklist for New Components
- [ ] Supports dark mode (`dark:` variants)
- [ ] Has ARIA labels for accessibility
- [ ] Documented with JSDoc-style comment
- [ ] Added to `/admin/components` page
- [ ] Added to this documentation
- [ ] Uses existing design tokens (colors, spacing)
- [ ] Tested in both light and dark mode
- [ ] Works on mobile viewport
---
## Component Reference Page
For live examples and copy-paste code, visit:
**Admin Panel:** `/admin/components`
This page includes:
- Interactive demos
- Code snippets with copy button
- All available macros
- Usage examples
---
## Related Documentation
- [UI Components Quick Reference](ui-components-quick-reference.md)
- [Icons Guide](../../development/icons-guide.md)
- [Tailwind CSS Guide](../tailwind-css.md)
- [Frontend Overview](../overview.md)
- [Architecture Rules](../../development/architecture-rules.md)

View File

@@ -116,6 +116,108 @@
</span>
```
## Number Stepper
A number input with +/- buttons for quantity selection. Ideal for cart quantities, batch sizes, and product pages.
### Basic Number Stepper
```html
{% from 'shared/macros/inputs.html' import number_stepper %}
{# Basic usage - cart quantity #}
{{ number_stepper(model='quantity', min=1, max=99) }}
```
### Size Variants
```html
{# Small - compact for tables/lists #}
{{ number_stepper(model='item.qty', min=1, max='item.stock', size='sm') }}
{# Medium (default) #}
{{ number_stepper(model='quantity', min=1, max=99) }}
{# Large - prominent placement #}
{{ number_stepper(model='batchSize', min=100, max=5000, step=100, size='lg') }}
```
### With Disabled State
```html
{{ number_stepper(model='qty', min=1, disabled_var='isLoading') }}
```
### Number Stepper Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `model` | required | Alpine.js x-model variable |
| `min` | `1` | Minimum allowed value |
| `max` | `none` | Maximum allowed value (can be Alpine.js expression) |
| `step` | `1` | Increment/decrement step |
| `size` | `'md'` | Size variant: `'sm'`, `'md'`, `'lg'` |
| `disabled_var` | `none` | Alpine.js variable for disabled state |
| `name` | `none` | Input name for form submission |
| `id` | `none` | Input id attribute |
| `label` | `'Quantity'` | Accessible label for screen readers |
---
## Tabs
Tab navigation components for switching between content sections.
### 🗂️ Navigation Tabs (with icons)
```html
{% from 'shared/macros/tabs.html' import tabs_nav, tab_button %}
{% call tabs_nav() %}
{{ tab_button('dashboard', 'Dashboard', icon='home') }}
{{ tab_button('settings', 'Settings', icon='cog') }}
{{ tab_button('profile', 'Profile', icon='user') }}
{% endcall %}
<!-- Tab content panels -->
<div x-show="activeTab === 'dashboard'" x-transition>
Dashboard content...
</div>
```
### 🗂️ Inline Tabs (with count badges)
```html
{% from 'shared/macros/tabs.html' import tabs_inline, tab_button %}
<div class="flex justify-between gap-4">
{% call tabs_inline() %}
{{ tab_button('all', 'All Items', count_var='allItems.length') }}
{{ tab_button('active', 'Active', count_var='activeItems.length') }}
{{ tab_button('archived', 'Archived', count_var='archivedItems.length') }}
{% endcall %}
<div>Search...</div>
</div>
```
### 🗂️ Tabs with Custom Click Handlers
```html
{% call tabs_nav() %}
{{ tab_button('database', 'Database Logs',
tab_var='logSource',
icon='database',
onclick="logSource = 'database'; loadDatabaseLogs()") }}
{{ tab_button('file', 'File Logs',
tab_var='logSource',
icon='document',
onclick="logSource = 'file'; loadFileLogs()") }}
{% endcall %}
```
### Tab Button Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `id` | required | Tab identifier for comparison |
| `label` | required | Display text |
| `tab_var` | `'activeTab'` | Alpine.js variable for active state |
| `icon` | `none` | Optional icon name |
| `count_var` | `none` | Alpine.js variable for count badge |
| `onclick` | `none` | Custom click handler (overrides default) |
## Grid Layouts
### 2 Columns (Desktop)
@@ -223,6 +325,138 @@
7. Helper text: `text-xs text-gray-600`
8. Error text: `text-xs text-red-600`
---
## E-commerce Components (Shop Frontend)
Reusable macros for shop/storefront functionality. Located in `app/templates/shared/macros/shop/`.
### 🛍️ Product Card
```html
{% from 'shared/macros/shop/product-card.html' import product_card %}
{# Basic product card #}
{{ product_card(product_var='product') }}
{# With size and options #}
{{ product_card(
product_var='item',
size='lg',
show_rating=true,
show_quick_add=true,
show_wishlist=true
) }}
```
**Size variants:** `sm` (compact), `md` (default), `lg` (featured)
**Features:**
- Sale badge (when `sale_price` exists)
- "New" badge (when `is_new` is true)
- Out of stock overlay
- Star ratings with review count
- Wishlist toggle button
- Quick add to cart
### 🛍️ Product Grid
```html
{% from 'shared/macros/shop/product-grid.html' import product_grid %}
{# Basic grid #}
{{ product_grid(products_var='products', loading_var='loading') }}
{# With empty state #}
{{ product_grid(
products_var='searchResults',
loading_var='searching',
empty_message='No products found',
empty_icon='search'
) }}
```
**Features:**
- Responsive columns (auto-adjusts or fixed)
- Loading skeleton placeholders
- Empty state with customizable icon/message
### 🛒 Add to Cart
```html
{% from 'shared/macros/shop/add-to-cart.html' import add_to_cart_button, add_to_cart_form, buy_now_button %}
{# Simple button #}
{{ add_to_cart_button(action='addToCart()') }}
{# Complete form with quantity #}
{{ add_to_cart_form(product_var='product', size='md') }}
{# Buy now button #}
{{ buy_now_button(action='buyNow()') }}
```
**Macros available:**
- `add_to_cart_button()` - Simple button with loading state
- `add_to_cart_form()` - Form with quantity selector + button
- `buy_now_button()` - Direct checkout button
- `shop_quantity_selector()` - Stock-aware quantity input
### 🛒 Mini Cart (Header)
```html
{% from 'shared/macros/shop/mini-cart.html' import mini_cart, mini_cart_icon %}
{# Complete mini cart with dropdown #}
{{ mini_cart(cart_var='cart', show_var='showCart') }}
{# Just the icon with badge #}
{{ mini_cart_icon(cart_var='cart', size='md') }}
```
**Macros available:**
- `mini_cart()` - Combined icon + dropdown
- `mini_cart_icon()` - Icon with item count badge
- `mini_cart_dropdown()` - Dropdown panel
- `cart_item()` - Individual item display
- `cart_summary()` - Subtotal, shipping, total
### E-commerce Alpine.js State
```javascript
// Required state variables for e-commerce components
{
// Products
products: [],
loading: true,
// Cart
cart: {
items: [],
item_count: 0,
subtotal: 0,
total: 0
},
showCart: false,
// Add to cart
quantity: 1,
addingToCart: false,
addedToCart: false,
// Wishlist
toggleWishlist(product) {
product.in_wishlist = !product.in_wishlist;
},
// Cart actions
addToCart() {
this.addingToCart = true;
// API call...
},
removeFromCart(itemId) {
this.cart.items = this.cart.items.filter(i => i.id !== itemId);
}
}
```
---
## Reference Page
Visit `/admin/components` for full component library with live examples!