Files
orion/docs/frontend/shared/ui-components-quick-reference.md
Samir Boulahtit 00857752cd docs: update UI components reference with Priority 4 and 5
Add documentation for all new e-commerce macros:

Priority 4 - Navigation & Discovery:
- Category Navigation: category_nav, mega_menu, mobile_category_drawer
- Breadcrumbs: breadcrumbs with variants and schema.org markup
- Search Bar: search_bar, search_autocomplete, mobile_search
- Filter Sidebar: filter_sidebar, price_filter, rating_filter, sort_dropdown

Priority 5 - Social Proof & Trust:
- Star Rating: star_rating, rating_input, rating_summary, compact_rating
- Reviews: review_card, review_list, review_form, review_summary_section
- Trust Badges: trust_badges, trust_banner, payment_icons, guarantee_badge,
  security_seals, checkout_trust_section

Also adds Alpine.js state variables for reviews and ratings.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 18:17:49 +01:00

21 KiB

UI Components Quick Reference

Most Common Patterns

📝 Form Field (Basic)

<label class="block mb-4 text-sm">
    <span class="text-gray-700 dark:text-gray-400">Field Name</span>
    <input
        type="text"
        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"
    />
</label>

📝 Required Field with Error

<label class="block mb-4 text-sm">
    <span class="text-gray-700 dark:text-gray-400">
        Field Name <span class="text-red-600">*</span>
    </span>
    <input
        type="text"
        x-model="formData.field"
        required
        :class="{ 'border-red-600': errors.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 form-input"
    />
    <span x-show="errors.field" class="text-xs text-red-600 dark:text-red-400" x-text="errors.field"></span>
</label>

📝 Read-Only Field

<label class="block mb-4 text-sm">
    <span class="text-gray-700 dark:text-gray-400">Field Name</span>
    <input
        type="text"
        x-model="data.field"
        disabled
        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"
    />
</label>

🃏 Stats Card

<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
    <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">Label</p>
        <p class="text-lg font-semibold text-gray-700 dark:text-gray-200">Value</p>
    </div>
</div>

🃏 Info Card

<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">Title</h3>
    <div class="space-y-3">
        <div>
            <p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Label</p>
            <p class="text-sm text-gray-700 dark:text-gray-300">Value</p>
        </div>
    </div>
</div>

🔘 Primary Button

<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">
    Click Me
</button>

🔘 Button with Icon

<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">
    <span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
    Add Item
</button>

🔘 Secondary Button

<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 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
    Cancel
</button>

🏷️ Status Badge (Success)

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

🏷️ Status Badge (Warning)

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

🏷️ Status Badge (Danger)

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

Number Stepper

A number input with +/- buttons for quantity selection. Ideal for cart quantities, batch sizes, and product pages.

Basic Number Stepper

{% from 'shared/macros/inputs.html' import number_stepper %}

{# Basic usage - cart quantity #}
{{ number_stepper(model='quantity', min=1, max=99) }}

Size Variants

{# 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

{{ 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)

{% 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)

{% 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

{% 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)

<div class="grid gap-6 md:grid-cols-2">
    <!-- Column 1 -->
    <div>...</div>
    <!-- Column 2 -->
    <div>...</div>
</div>

4 Columns (Responsive)

<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
    <!-- Cards -->
</div>

Color Classes

Background Colors

  • Primary: bg-purple-600
  • Success: bg-green-600
  • Warning: bg-orange-600
  • Danger: bg-red-600
  • Info: bg-blue-600

Text Colors

  • Primary: text-purple-600
  • Success: text-green-600
  • Warning: text-orange-600
  • Danger: text-red-600
  • Info: text-blue-600

Icon Colors

  • Primary: text-purple-500 bg-purple-100
  • Success: text-green-500 bg-green-100
  • Warning: text-orange-500 bg-orange-100
  • Danger: text-red-500 bg-red-100
  • Info: text-blue-500 bg-blue-100

Common Icons

  • user-group - Users/Teams
  • badge-check - Verified
  • check-circle - Success
  • x-circle - Error/Inactive
  • clock - Pending
  • calendar - Dates
  • refresh - Update
  • edit - Edit
  • delete - Delete
  • plus - Add
  • arrow-left - Back
  • exclamation - Warning

Spacing

  • Small gap: gap-3
  • Medium gap: gap-6
  • Large gap: gap-8
  • Margin bottom: mb-4, mb-6, mb-8
  • Padding: p-3, p-4, px-4 py-3

Quick Copy-Paste: Page Structure

{# app/templates/admin/your-page.html #}
{% extends "admin/base.html" %}

{% block title %}Your Page{% endblock %}

{% block alpine_data %}yourPageData(){% endblock %}

{% block content %}
<!-- Page Header -->
<div class="my-6">
    <h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
        Page Title
    </h2>
</div>

<!-- Loading State -->
<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>

<!-- Content -->
<div x-show="!loading">
    <div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
        <!-- Your content here -->
    </div>
</div>
{% endblock %}

Remember

  1. Always use dark: variants for dark mode
  2. Add :disabled="saving" to buttons during operations
  3. Use x-show for conditional display
  4. Use x-text for dynamic text
  5. Use x-html="$icon(...)" for icons
  6. Validation errors: border-red-600 class
  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

{% 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

{% 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

{% 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)

{% 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
{% from 'shared/macros/shop/product-gallery.html' import product_gallery %}

{# Full gallery with thumbnails, zoom, and lightbox #}
{{ product_gallery(images_var='product.images', enable_zoom=true, enable_lightbox=true) }}

{# Simple single image viewer #}
{{ simple_image_viewer(image_var='product.image_url') }}

Features:

  • Thumbnail navigation
  • Hover zoom on main image
  • Fullscreen lightbox with keyboard navigation
  • Responsive design

🎨 Variant Selector (Priority 3)

{% from 'shared/macros/shop/variant-selector.html' import size_selector, color_swatches %}

{# Size buttons with size guide link #}
{{ size_selector(sizes_var='product.sizes', show_guide=true) }}

{# Color swatches with out-of-stock indicators #}
{{ color_swatches(colors_var='product.colors', selected_var='selectedColor') }}

Macros available:

  • variant_selector() - Generic (buttons/dropdown/swatches)
  • size_selector() - Specialized for sizes
  • color_swatches() - Color selection with hex preview
  • multi_variant_selector() - Multiple option types

📄 Product Info (Priority 3)

{% from 'shared/macros/shop/product-info.html' import product_info, product_price %}

{# Complete info block #}
{{ product_info(product_var='product', show_vendor=true, show_rating=true) }}

{# Individual components #}
{{ product_price(product_var='product', size='lg') }}
{{ product_rating(product_var='product', clickable=true) }}
{{ stock_status(product_var='product') }}

Macros available:

  • product_info() - Complete info section
  • product_price() - Price with sale support
  • product_rating() - Star rating display
  • stock_status() - Stock indicator
  • trust_indicators() - Shipping/returns badges

📑 Product Tabs (Priority 3)

{% from 'shared/macros/shop/product-tabs.html' import product_tabs %}

{# Tabbed product information #}
{{ product_tabs(
    product_var='product',
    tabs=['description', 'specifications', 'reviews', 'shipping']
) }}

Tab options: description, specifications, reviews, shipping, warranty

📂 Category Navigation (Priority 4)

{% from 'shared/macros/shop/category-nav.html' import category_nav, category_tree, category_menu %}

{# Sidebar with nested categories #}
{{ category_nav(categories_var='categories', current_var='currentCategory', show_count=true) }}

{# Horizontal pills #}
{{ category_tree(categories_var='categories', layout='horizontal') }}

{# Header dropdown menu #}
{{ category_menu(categories_var='mainCategories') }}

Macros: category_nav(), category_tree(), category_menu(), category_drawer()

🍞 Breadcrumbs (Priority 4)

{% from 'shared/macros/shop/breadcrumbs.html' import shop_breadcrumbs, compact_breadcrumbs %}

{# Static breadcrumbs #}
{{ shop_breadcrumbs(items=[
    {'label': 'Electronics', 'url': '/electronics'},
    {'label': 'Audio', 'url': '/audio'},
    {'label': 'Headphones'}
]) }}

{# Mobile-friendly compact #}
{{ compact_breadcrumbs(parent={'label': 'Audio', 'url': '/audio'}, current='Headphones') }}

Macros: shop_breadcrumbs(), auto_breadcrumbs(), compact_breadcrumbs()

🔍 Search Bar (Priority 4)

{% from 'shared/macros/shop/search-bar.html' import search_bar, search_autocomplete %}

{# Basic search #}
{{ search_bar(placeholder='Search products...') }}

{# With search button #}
{{ search_bar(placeholder='Search...', show_button=true, size='lg') }}

{# Autocomplete with suggestions #}
{{ search_autocomplete(search_endpoint='/api/search', show_recent=true, show_popular=true) }}

Macros: search_bar(), search_autocomplete(), mobile_search(), search_trigger()

🎛️ Filter Sidebar (Priority 4)

{% from 'shared/macros/shop/filter-sidebar.html' import filter_sidebar, price_filter, sort_dropdown %}

{# Complete filter panel #}
{{ filter_sidebar(filters_var='filters', active_filters_var='activeFilters', on_change='filterProducts()') }}

{# Standalone price filter #}
{{ price_filter(min=0, max=500, on_change='updateFilters()') }}

{# Sort dropdown #}
{{ sort_dropdown(value_var='sortBy', on_change='sortProducts()') }}

Macros: filter_sidebar(), price_filter(), rating_filter(), sort_dropdown(), mobile_filter_drawer()

Star Rating (Priority 5)

{% from 'shared/macros/shop/star-rating.html' import star_rating, rating_input, rating_summary, compact_rating %}

{# Static star rating with half-star support #}
{{ star_rating(rating=4.5, show_value=true, show_count=true, count=127) }}

{# Dynamic rating from Alpine.js #}
{{ star_rating(rating_var='product.rating', show_count=true, count_var='product.review_count') }}

{# Interactive rating input #}
{{ rating_input(model='userRating', size='lg', allow_half=true) }}

{# Rating summary with distribution bars #}
{{ rating_summary(rating_var='rating', count_var='reviewCount', distribution_var='ratingDistribution') }}

{# Compact rating for lists/cards #}
{{ compact_rating(rating=4.5, count=127, size='sm') }}

Macros: star_rating(), rating_input(), rating_summary(), compact_rating()

💬 Reviews (Priority 5)

{% from 'shared/macros/shop/reviews.html' import review_card, review_list, review_form, review_summary_section %}

{# Single review card with helpful buttons #}
{{ review_card(review_var='review', on_helpful='markHelpful(review.id)') }}

{# Review list with sorting #}
{{ review_list(reviews_var='reviews', sort_var='reviewSort', on_helpful='markHelpful') }}

{# Review submission form #}
{{ review_form(rating_model='rating', title_model='title', content_model='content', on_submit='submitReview()') }}

{# Complete review summary section #}
{{ review_summary_section(rating_var='rating', count_var='count', distribution_var='dist', on_write='openForm()') }}

Macros: review_card(), review_list(), review_form(), review_summary_section()

🛡️ Trust Badges (Priority 5)

{% from 'shared/macros/shop/trust-badges.html' import trust_badges, trust_banner, payment_icons, guarantee_badge, security_seals, checkout_trust_section %}

{# Trust badges grid #}
{{ trust_badges(badges=['secure_payment', 'free_shipping', 'easy_returns', 'support_24_7'], layout='grid') }}

{# Trust banner variants #}
{{ trust_banner() }}
{{ trust_banner(variant='compact') }}
{{ trust_banner(variant='detailed') }}

{# Payment method icons #}
{{ payment_icons(methods=['visa', 'mastercard', 'paypal', 'apple_pay']) }}
{{ payment_icons(methods=['visa', 'mastercard'], size='lg') }}

{# Guarantee badges #}
{{ guarantee_badge(type='money_back', days=30) }}
{{ guarantee_badge(type='satisfaction', variant='filled') }}

{# Security seals #}
{{ security_seals(seals=['ssl', 'verified']) }}

{# Complete checkout trust section #}
{{ checkout_trust_section() }}

Macros: trust_badges(), trust_banner(), payment_icons(), guarantee_badge(), security_seals(), checkout_trust_section()

E-commerce Alpine.js State

// 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,

    // Product detail (Priority 3)
    selectedImage: 0,
    selectedSize: null,
    selectedColor: null,
    activeProductTab: 'description',

    // Navigation & Discovery (Priority 4)
    categories: [],
    currentCategory: null,
    breadcrumbs: [],

    // Filters (Priority 4)
    filters: {
        categories: [],
        brands: [],
        priceRange: { min: 0, max: 1000 },
        attributes: {},
        ratings: []
    },
    activeFilters: {
        categories: [],
        brands: [],
        priceMin: undefined,
        priceMax: undefined,
        rating: undefined,
        attributes: {},
        inStock: false
    },
    sortBy: 'relevance',

    // Mobile UI (Priority 4)
    showMobileSearch: false,
    showMobileFilters: false,
    showCategoryDrawer: false,

    // Reviews & Ratings (Priority 5)
    reviews: [],
    reviewSort: 'newest',
    userRating: 0,
    ratingDistribution: { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 },
    newReview: { rating: 0, title: '', content: '' },
    submittingReview: false,
    showReviewForm: 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);
    },

    // Filter actions (Priority 4)
    filterProducts() {
        // Apply filters and update products...
    },
    sortProducts() {
        // Sort products by sortBy value...
    },

    // Review actions (Priority 5)
    submitReview() {
        this.submittingReview = true;
        // API call to submit review...
    },
    markHelpful(reviewId, helpful) {
        // Mark review as helpful/not helpful...
    }
}

Reference Page

Visit /admin/components for full component library with live examples!