feat: add Priority 4 and 5 demos to components showcase page

Add interactive demos for all new e-commerce macros:

Priority 4 - Navigation & Discovery:
- Category navigation with mega menu
- Breadcrumb variants
- Search bar with autocomplete
- Filter sidebar with all filter types
- Sort dropdown

Priority 5 - Social Proof & Trust:
- Star ratings (static, dynamic, sizes)
- Compact ratings for lists
- Interactive rating input
- Rating summary with distribution
- Review cards with helpful voting
- Review form
- Trust badges grid
- Trust banner variants
- Payment method icons
- Guarantee badges
- Security seals
- Checkout trust section

Demo state includes sample data for categories, filters,
reviews, and ratings to showcase all component features.

🤖 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 18:17:30 +01:00
parent 43896a8644
commit 8fe1315cac
2 changed files with 931 additions and 0 deletions

View File

@@ -619,6 +619,756 @@ html {
Copy Code
</button>
</div>
<!-- Category Navigation Demo (Priority 4) -->
<div class="mb-8 pt-8 border-t border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Category Navigation</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
Sidebar navigation with nested categories, collapsible sections, and active state tracking.
</p>
<div class="grid lg:grid-cols-2 gap-6">
{# Sidebar Navigation #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3">Sidebar Navigation</h4>
<nav
x-data="{
expandedCategories: new Set([1, 11]),
toggleCategory(id) {
if (this.expandedCategories.has(id)) {
this.expandedCategories.delete(id);
} else {
this.expandedCategories.add(id);
}
},
isExpanded(id) {
return this.expandedCategories.has(id);
},
isActive(category) {
return currentCategory?.id === category.id;
}
}"
class="space-y-1"
>
<template x-for="category in demoCategories" :key="category.id">
<div>
<div class="flex items-center">
<a
:href="category.url"
class="flex-1 flex items-center justify-between px-3 py-2 text-sm rounded-lg transition-colors"
:class="{
'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 font-medium': isActive(category),
'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700': !isActive(category)
}"
@click.prevent
>
<span x-text="category.name"></span>
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="'(' + category.product_count + ')'"></span>
</a>
<button
x-show="category.children?.length"
type="button"
@click="toggleCategory(category.id)"
class="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<span x-html="$icon('chevron-right', 'w-4 h-4 transition-transform')" :class="isExpanded(category.id) && 'rotate-90'"></span>
</button>
</div>
<div x-show="isExpanded(category.id) && category.children?.length" x-collapse class="ml-4 mt-1 space-y-1">
<template x-for="child in category.children" :key="child.id">
<a
:href="child.url"
class="flex items-center justify-between px-3 py-2 text-sm rounded-lg transition-colors"
:class="{
'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 font-medium': isActive(child),
'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700': !isActive(child)
}"
@click.prevent
>
<span x-text="child.name"></span>
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="'(' + child.product_count + ')'"></span>
</a>
</template>
</div>
</div>
</template>
</nav>
</div>
{# Horizontal Category Menu #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3">Horizontal Pills</h4>
<nav class="flex flex-wrap gap-2">
<template x-for="category in demoCategories" :key="category.id">
<a
:href="category.url"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors"
:class="currentCategory?.id === category.id ? 'bg-purple-600 text-white' : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'"
@click.prevent
>
<span x-text="category.name"></span>
<span class="ml-1 text-xs opacity-75" x-text="'(' + category.product_count + ')'"></span>
</a>
</template>
</nav>
</div>
</div>
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/category-nav.html' import category_nav, category_tree %}
{# Sidebar with nested categories #}
{{ category_nav(categories_var='categories', current_var='currentCategory', show_count=true) }}
{# Horizontal pills #}
{{ category_tree(categories_var='categories', layout='horizontal') }}{% endraw %}`)" class="mt-4 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
Copy Code
</button>
</div>
<!-- Breadcrumbs Demo (Priority 4) -->
<div class="mb-8 pt-8 border-t border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Breadcrumbs</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
Navigation trail showing category hierarchy with home link, separator icons, and current page indicator.
</p>
<div class="space-y-6">
{# Standard Breadcrumbs #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3">Standard Breadcrumbs</h4>
<nav aria-label="Breadcrumb" class="flex items-center text-sm">
<ol class="flex items-center flex-wrap gap-1">
<li class="flex items-center">
<a href="#" class="flex items-center gap-1 text-gray-500 dark:text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 transition-colors" @click.prevent>
<span x-html="$icon('home', 'w-4 h-4')"></span>
<span class="sr-only sm:not-sr-only">Home</span>
</a>
</li>
<li class="flex items-center text-gray-400 dark:text-gray-500">
<span x-html="$icon('chevron-right', 'w-4 h-4')"></span>
</li>
<template x-for="(item, index) in demoBreadcrumbs" :key="index">
<li class="flex items-center">
<template x-if="item.url && index < demoBreadcrumbs.length - 1">
<a :href="item.url" class="text-gray-500 dark:text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 transition-colors" x-text="item.label" @click.prevent></a>
</template>
<template x-if="!item.url || index === demoBreadcrumbs.length - 1">
<span class="text-gray-900 dark:text-white font-medium" x-text="item.label"></span>
</template>
<span x-show="index < demoBreadcrumbs.length - 1" class="mx-2 text-gray-400 dark:text-gray-500" x-html="$icon('chevron-right', 'w-4 h-4')"></span>
</li>
</template>
</ol>
</nav>
</div>
{# Compact Breadcrumbs #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3">Compact (Mobile-friendly)</h4>
<nav aria-label="Breadcrumb" class="flex items-center text-sm">
<a href="#" class="flex items-center gap-1 text-gray-500 dark:text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 transition-colors" @click.prevent>
<span x-html="$icon('arrow-left', 'w-4 h-4')"></span>
<span>Audio</span>
</a>
<span class="mx-2 text-gray-400 dark:text-gray-500">/</span>
<span class="text-gray-900 dark:text-white font-medium">Headphones</span>
</nav>
</div>
</div>
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/breadcrumbs.html' import shop_breadcrumbs, compact_breadcrumbs %}
{# Static breadcrumbs #}
{{ shop_breadcrumbs(items=[
{'label': 'Electronics', 'url': '/category/electronics'},
{'label': 'Audio', 'url': '/category/audio'},
{'label': 'Headphones'}
]) }}
{# Dynamic from Alpine.js #}
{{ shop_breadcrumbs(items_var='breadcrumbs') }}
{# Compact for mobile #}
{{ compact_breadcrumbs(parent={'label': 'Audio', 'url': '/audio'}, current='Headphones') }}{% endraw %}`)" class="mt-4 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
Copy Code
</button>
</div>
<!-- Search Bar Demo (Priority 4) -->
<div class="mb-8 pt-8 border-t border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Search Bar</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
Product search with autocomplete, recent searches, popular suggestions, and mobile-optimized full-screen search.
</p>
<div class="grid lg:grid-cols-2 gap-6">
{# Basic Search #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3">Basic Search</h4>
<form class="relative flex items-center gap-2" @submit.prevent>
<div class="relative flex-1">
<span class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none">
<span x-html="$icon('search', 'w-full h-full')"></span>
</span>
<input
type="search"
placeholder="Search products..."
class="w-full py-2.5 pl-10 pr-4 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:border-purple-500 dark:focus:border-purple-400 focus:ring-2 focus:ring-purple-500/20 outline-none transition-colors"
>
</div>
</form>
</div>
{# Search with Button #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3">With Search Button</h4>
<form class="relative flex items-center gap-2" @submit.prevent>
<div class="relative flex-1">
<span class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none">
<span x-html="$icon('search', 'w-full h-full')"></span>
</span>
<input
type="search"
placeholder="What are you looking for?"
class="w-full py-2.5 pl-10 pr-4 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:border-purple-500 dark:focus:border-purple-400 focus:ring-2 focus:ring-purple-500/20 outline-none transition-colors"
>
</div>
<button type="submit" class="px-4 py-2.5 text-sm bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-lg transition-colors">
Search
</button>
</form>
</div>
{# Autocomplete Search #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 lg:col-span-2">
<h4 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3">With Autocomplete Dropdown</h4>
<div
x-data="{
query: '',
isOpen: false,
recentSearches: ['Wireless headphones', 'Smart watch', 'Bluetooth speaker'],
popularSearches: ['Electronics', 'Clothing', 'Home & Garden', 'Sports']
}"
class="relative max-w-xl"
@click.away="isOpen = false"
>
<form @submit.prevent>
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none">
<span x-html="$icon('search', 'w-full h-full')"></span>
</span>
<input
type="search"
x-model="query"
@focus="isOpen = true"
placeholder="Search products..."
autocomplete="off"
class="w-full py-2.5 pl-10 pr-10 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:border-purple-500 dark:focus:border-purple-400 focus:ring-2 focus:ring-purple-500/20 outline-none transition-colors"
>
<button
type="button"
x-show="query.length > 0"
@click="query = ''"
class="absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<span x-html="$icon('x', 'w-full h-full')"></span>
</button>
</div>
</form>
<div
x-show="isOpen && query.length < 2"
x-transition
class="absolute left-0 right-0 mt-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 overflow-hidden z-50"
>
<div class="py-2">
<div class="flex items-center justify-between px-4 py-2">
<span class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Recent Searches</span>
<button type="button" class="text-xs text-purple-600 dark:text-purple-400 hover:underline">Clear</button>
</div>
<template x-for="term in recentSearches" :key="term">
<button type="button" @click="query = term" class="w-full flex items-center gap-3 px-4 py-2 text-left text-sm hover:bg-gray-50 dark:hover:bg-gray-700/50 text-gray-700 dark:text-gray-300">
<span x-html="$icon('clock', 'w-4 h-4 text-gray-400 flex-shrink-0')"></span>
<span x-text="term"></span>
</button>
</template>
<div class="border-t border-gray-200 dark:border-gray-700 mt-2 pt-2">
<div class="px-4 py-2">
<span class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Popular Searches</span>
</div>
<template x-for="term in popularSearches" :key="term">
<button type="button" @click="query = term" class="w-full flex items-center gap-3 px-4 py-2 text-left text-sm hover:bg-gray-50 dark:hover:bg-gray-700/50 text-gray-700 dark:text-gray-300">
<span x-html="$icon('trending-up', 'w-4 h-4 text-gray-400 flex-shrink-0')"></span>
<span x-text="term"></span>
</button>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
<button @click="copyCode(`{% raw %}{% 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='What are you looking for?', show_button=true) }}
{# With autocomplete dropdown #}
{{ search_autocomplete(search_endpoint='/api/products/search', show_recent=true, show_popular=true) }}{% endraw %}`)" class="mt-4 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
Copy Code
</button>
</div>
<!-- Filter Sidebar Demo (Priority 4) -->
<div class="mb-8 pt-8 border-t border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Filter Sidebar</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
Product filtering with price range, categories, brands, colors, sizes, ratings, and availability.
</p>
<div class="grid lg:grid-cols-3 gap-6">
{# Filter Sidebar #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<div class="flex items-center justify-between mb-4">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">Filters</h4>
<button type="button" x-show="demoActiveFilters.categories?.length > 0 || demoActiveFilters.brands?.length > 0" @click="demoActiveFilters = {categories: [], brands: [], priceMin: undefined, priceMax: undefined, rating: undefined, attributes: {}, inStock: false}" class="text-xs text-purple-600 dark:text-purple-400 hover:underline">Clear all</button>
</div>
{# Categories #}
<div class="border-t border-gray-200 dark:border-gray-700 pt-4 mb-4">
<h5 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Categories</h5>
<div class="space-y-2">
<template x-for="cat in demoFilters.categories" :key="cat.id">
<label class="flex items-center gap-3 cursor-pointer group">
<input type="checkbox" :value="cat.id" :checked="demoActiveFilters.categories?.includes(cat.id)" @change="demoActiveFilters.categories?.includes(cat.id) ? demoActiveFilters.categories = demoActiveFilters.categories.filter(c => c !== cat.id) : demoActiveFilters.categories = [...(demoActiveFilters.categories || []), cat.id]" class="w-4 h-4 rounded border-gray-300 dark:border-gray-600 text-purple-600 focus:ring-purple-500">
<span class="flex-1 text-sm text-gray-700 dark:text-gray-300" x-text="cat.name"></span>
<span class="text-xs text-gray-400" x-text="'(' + cat.count + ')'"></span>
</label>
</template>
</div>
</div>
{# Price Range #}
<div class="border-t border-gray-200 dark:border-gray-700 pt-4 mb-4">
<h5 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Price Range</h5>
<div class="flex items-center gap-3">
<div class="flex-1">
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm">$</span>
<input type="number" x-model.number="demoActiveFilters.priceMin" placeholder="Min" class="w-full pl-7 pr-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
</div>
</div>
<span class="text-gray-400">-</span>
<div class="flex-1">
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm">$</span>
<input type="number" x-model.number="demoActiveFilters.priceMax" placeholder="Max" class="w-full pl-7 pr-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
</div>
</div>
</div>
</div>
{# Availability #}
<div class="border-t border-gray-200 dark:border-gray-700 pt-4">
<label class="flex items-center gap-3 cursor-pointer">
<input type="checkbox" x-model="demoActiveFilters.inStock" class="w-4 h-4 rounded border-gray-300 dark:border-gray-600 text-purple-600 focus:ring-purple-500">
<span class="text-sm text-gray-700 dark:text-gray-300">In Stock Only</span>
</label>
</div>
</div>
{# Color & Size Filters #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-4">Attribute Filters</h4>
{# Colors #}
<div class="mb-4">
<h5 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Color</h5>
<div class="flex flex-wrap gap-2">
<template x-for="color in demoFilters.attributes.color" :key="color.value">
<button type="button" @click="!demoActiveFilters.attributes.color ? demoActiveFilters.attributes.color = [color.value] : (demoActiveFilters.attributes.color.includes(color.value) ? demoActiveFilters.attributes.color = demoActiveFilters.attributes.color.filter(c => c !== color.value) : demoActiveFilters.attributes.color.push(color.value))" class="w-8 h-8 rounded-full border-2 transition-all" :class="demoActiveFilters.attributes.color?.includes(color.value) ? 'border-purple-500 ring-2 ring-purple-500/30' : 'border-gray-300 dark:border-gray-600 hover:border-gray-400'" :style="'background-color: ' + color.hex" :title="color.label">
<span class="sr-only" x-text="color.label"></span>
<span x-show="demoActiveFilters.attributes.color?.includes(color.value)" x-html="$icon('check', 'w-4 h-4 mx-auto')" :class="color.value === 'white' ? 'text-gray-800' : 'text-white'"></span>
</button>
</template>
</div>
</div>
{# Sizes #}
<div>
<h5 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Size</h5>
<div class="flex flex-wrap gap-2">
<template x-for="size in demoFilters.attributes.size" :key="size.value">
<button type="button" @click="!demoActiveFilters.attributes.size ? demoActiveFilters.attributes.size = [size.value] : (demoActiveFilters.attributes.size.includes(size.value) ? demoActiveFilters.attributes.size = demoActiveFilters.attributes.size.filter(s => s !== size.value) : demoActiveFilters.attributes.size.push(size.value))" class="px-3 py-1.5 text-sm border rounded-lg transition-colors" :class="demoActiveFilters.attributes.size?.includes(size.value) ? 'bg-purple-600 text-white border-purple-600' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600 hover:border-purple-500'">
<span x-text="size.label"></span>
</button>
</template>
</div>
</div>
</div>
{# Rating & Sort #}
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-4">Rating & Sort</h4>
{# Rating Filter #}
<div class="mb-4">
<h5 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Rating</h5>
<div class="space-y-2">
<template x-for="rating in demoFilters.ratings.slice(0, 4)" :key="rating.value">
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="demo-rating" :value="rating.value" :checked="demoActiveFilters.rating === rating.value" @change="demoActiveFilters.rating = rating.value" class="w-4 h-4 border-gray-300 dark:border-gray-600 text-purple-600 focus:ring-purple-500">
<span class="flex items-center gap-0.5">
<template x-for="i in 5" :key="i">
<span x-html="$icon('star', 'w-4 h-4')" :class="i <= rating.value ? 'text-yellow-400' : 'text-gray-300 dark:text-gray-600'"></span>
</template>
<span class="text-sm text-gray-500 dark:text-gray-400 ml-1">& up</span>
</span>
</label>
</template>
</div>
</div>
{# Sort Dropdown #}
<div>
<h5 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Sort By</h5>
<div x-data="{ open: false }" class="relative" @click.away="open = false">
<button type="button" @click="open = !open" class="w-full flex items-center justify-between px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-700 dark:text-gray-300">
<span x-text="[{value: 'relevance', label: 'Relevance'}, {value: 'price_asc', label: 'Price: Low to High'}, {value: 'price_desc', label: 'Price: High to Low'}, {value: 'newest', label: 'Newest First'}].find(o => o.value === demoSortBy)?.label || 'Relevance'"></span>
<span x-html="$icon('chevron-down', 'w-4 h-4 text-gray-400')"></span>
</button>
<div x-show="open" x-transition class="absolute left-0 right-0 mt-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50">
<button type="button" @click="demoSortBy = 'relevance'; open = false" class="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700" :class="demoSortBy === 'relevance' ? 'text-purple-600 dark:text-purple-400 font-medium' : 'text-gray-700 dark:text-gray-300'">Relevance</button>
<button type="button" @click="demoSortBy = 'price_asc'; open = false" class="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700" :class="demoSortBy === 'price_asc' ? 'text-purple-600 dark:text-purple-400 font-medium' : 'text-gray-700 dark:text-gray-300'">Price: Low to High</button>
<button type="button" @click="demoSortBy = 'price_desc'; open = false" class="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700" :class="demoSortBy === 'price_desc' ? 'text-purple-600 dark:text-purple-400 font-medium' : 'text-gray-700 dark:text-gray-300'">Price: High to Low</button>
<button type="button" @click="demoSortBy = 'newest'; open = false" class="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700" :class="demoSortBy === 'newest' ? 'text-purple-600 dark:text-purple-400 font-medium' : 'text-gray-700 dark:text-gray-300'">Newest First</button>
</div>
</div>
</div>
</div>
</div>
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/filter-sidebar.html' import filter_sidebar, price_filter, rating_filter, sort_dropdown %}
{# Complete filter sidebar #}
{{ filter_sidebar(filters_var='filters', active_filters_var='activeFilters', on_change='filterProducts()') }}
{# Standalone price filter #}
{{ price_filter(min=0, max=500, on_change='updateFilters()') }}
{# Rating filter #}
{{ rating_filter(value_var='minRating', on_change='filterByRating()') }}
{# Sort dropdown #}
{{ sort_dropdown(value_var='sortBy', on_change='sortProducts()') }}{% endraw %}`)" class="mt-4 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
Copy Code
</button>
</div>
</div>
</section>
<!-- PRIORITY 5: SOCIAL PROOF & TRUST -->
<!-- Star Rating -->
<section id="star-rating" class="scroll-mt-24">
{% from 'shared/macros/shop/star-rating.html' import star_rating, rating_input, rating_summary, compact_rating %}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-6 flex items-center">
<span x-html="$icon('star', 'w-6 h-6 mr-2 text-yellow-400')"></span>
Star Rating
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-6">
Flexible star rating components for displaying and collecting ratings.
</p>
<div class="space-y-8">
<!-- Static Star Ratings -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Static Ratings</h3>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Full Stars</p>
{{ star_rating(rating=4) }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Half Stars</p>
{{ star_rating(rating=4.5) }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Exact Precision</p>
{{ star_rating(rating=3.7, precision='exact') }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">With Value</p>
{{ star_rating(rating=4.5, show_value=true) }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">With Count</p>
{{ star_rating(rating=4.5, show_count=true, count=127) }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Sizes</p>
<div class="space-y-2">
{{ star_rating(rating=4, size='xs') }}
{{ star_rating(rating=4, size='sm') }}
{{ star_rating(rating=4, size='md') }}
{{ star_rating(rating=4, size='lg') }}
</div>
</div>
</div>
</div>
<!-- Dynamic Rating -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Dynamic Rating (Alpine.js)</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<div class="flex items-center gap-4 mb-4">
<span class="text-sm text-gray-500 dark:text-gray-400">Adjust rating:</span>
<input type="range" min="0" max="5" step="0.5" x-model.number="demoRating" class="w-48">
<span class="text-sm font-medium text-gray-900 dark:text-white" x-text="demoRating"></span>
</div>
{{ star_rating(rating_var='demoRating', show_value=true, show_count=true, count_var='demoReviewCount') }}
</div>
</div>
<!-- Compact Rating -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Compact Rating</h3>
<div class="grid gap-4 md:grid-cols-3">
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Extra Small</p>
{{ compact_rating(rating=4.5, count=127, size='xs') }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Small</p>
{{ compact_rating(rating=4.5, count=127, size='sm') }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Medium</p>
{{ compact_rating(rating=4.5, count=127, size='md') }}
</div>
</div>
</div>
<!-- Rating Input -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Interactive Rating Input</h3>
<div class="grid gap-6 md:grid-cols-2">
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Standard Input</p>
{{ rating_input(model='demoUserRating', size='md') }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Large with Half Stars</p>
{{ rating_input(model='demoUserRating', size='lg', allow_half=true) }}
</div>
</div>
</div>
<!-- Rating Summary -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Rating Summary</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
{{ rating_summary(rating_var='demoRating', count_var='demoReviewCount', distribution_var='demoRatingDistribution') }}
</div>
</div>
</div>
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/star-rating.html' import star_rating, rating_input, rating_summary, compact_rating %}
{# Static star rating #}
{{ star_rating(rating=4.5) }}
{{ 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') }}
{# Compact rating for lists #}
{{ compact_rating(rating=4.5, count=127, size='sm') }}
{# Interactive rating input #}
{{ rating_input(model='userRating', size='lg', allow_half=true) }}
{# Rating summary with distribution #}
{{ rating_summary(rating_var='rating', count_var='reviewCount', distribution_var='ratingDistribution') }}{% endraw %}`)" class="mt-4 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
Copy Code
</button>
</div>
</section>
<!-- Reviews -->
<section id="reviews" class="scroll-mt-24">
{% from 'shared/macros/shop/reviews.html' import review_card, review_list, review_form, review_summary_section %}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-6 flex items-center">
<span x-html="$icon('chat', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
Reviews
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-6">
Complete review system with cards, lists, forms, and summary sections.
</p>
<div class="space-y-8">
<!-- Review Card -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Review Card</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<template x-for="review in demoReviews.slice(0, 2)" :key="review.id">
<div class="mb-4 last:mb-0">
{{ review_card(review_var='review', on_helpful='demoMarkHelpful(review.id)') }}
</div>
</template>
</div>
</div>
<!-- Review Summary Section -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Review Summary Section</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
{{ review_summary_section(
rating_var='demoRating',
count_var='demoReviewCount',
distribution_var='demoRatingDistribution',
on_write='showReviewForm = true'
) }}
</div>
</div>
<!-- Review Form -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Review Form</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
{{ review_form(
rating_model='demoNewReview.rating',
title_model='demoNewReview.title',
content_model='demoNewReview.content',
on_submit='demoSubmitReview()',
submitting_var='submittingReview'
) }}
</div>
</div>
</div>
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/reviews.html' import review_card, review_list, review_form, review_summary_section %}
{# Single review card #}
{{ 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='newReview.rating',
title_model='newReview.title',
content_model='newReview.content',
on_submit='submitReview()',
submitting_var='submitting'
) }}
{# Complete review summary section #}
{{ review_summary_section(
rating_var='product.rating',
count_var='product.review_count',
distribution_var='product.rating_distribution',
on_write='openReviewForm()'
) }}{% endraw %}`)" class="mt-4 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
Copy Code
</button>
</div>
</section>
<!-- Trust Badges -->
<section id="trust-badges" class="scroll-mt-24">
{% from 'shared/macros/shop/trust-badges.html' import trust_badges, trust_banner, payment_icons, guarantee_badge, security_seals, checkout_trust_section %}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-6 flex items-center">
<span x-html="$icon('shield-check', 'w-6 h-6 mr-2 text-green-600 dark:text-green-400')"></span>
Trust Badges
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-6">
Trust signals and security indicators to build customer confidence.
</p>
<div class="space-y-8">
<!-- Trust Badges Grid -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Trust Badges</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
{{ trust_badges(badges=['secure_payment', 'free_shipping', 'easy_returns', 'support_24_7'], layout='grid', size='md') }}
</div>
</div>
<!-- Trust Banner -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Trust Banner</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
{{ trust_banner(variant='detailed') }}
</div>
</div>
<!-- Payment Icons -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Payment Icons</h3>
<div class="grid gap-4 md:grid-cols-2">
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">All Methods</p>
{{ payment_icons(methods=['visa', 'mastercard', 'paypal', 'apple_pay', 'google_pay', 'amex']) }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">With Label</p>
{{ payment_icons(methods=['visa', 'mastercard', 'paypal'], size='lg') }}
</div>
</div>
</div>
<!-- Guarantee Badge -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Guarantee Badges</h3>
<div class="grid gap-4 md:grid-cols-2">
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Money Back</p>
{{ guarantee_badge(type='money_back', days=30) }}
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Satisfaction</p>
{{ guarantee_badge(type='satisfaction', variant='filled') }}
</div>
</div>
</div>
<!-- Security Seals -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Security Seals</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
{{ security_seals(seals=['ssl', 'secure_checkout', 'verified']) }}
</div>
</div>
<!-- Checkout Trust Section -->
<div>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Checkout Trust Section</h3>
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
{{ checkout_trust_section() }}
</div>
</div>
</div>
<button @click="copyCode(`{% raw %}{% 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() }}
{{ checkout_trust_section(show_guarantee=true, show_payment=true, show_security=false) }}{% endraw %}`)" class="mt-4 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
Copy Code
</button>
</div>
</section>

View File

@@ -223,6 +223,187 @@ function adminComponents() {
selectedColor: null,
activeProductTab: 'description',
// Navigation & Discovery Demo (Priority 4)
demoCategories: [
{
id: 1,
name: 'Electronics',
slug: 'electronics',
url: '/category/electronics',
product_count: 245,
children: [
{ id: 11, name: 'Audio', slug: 'audio', url: '/category/audio', product_count: 89, children: [
{ id: 111, name: 'Headphones', slug: 'headphones', url: '/category/headphones', product_count: 45 },
{ id: 112, name: 'Speakers', slug: 'speakers', url: '/category/speakers', product_count: 32 }
]},
{ id: 12, name: 'Wearables', slug: 'wearables', url: '/category/wearables', product_count: 67 },
{ id: 13, name: 'Accessories', slug: 'accessories', url: '/category/accessories', product_count: 89 }
]
},
{
id: 2,
name: 'Clothing',
slug: 'clothing',
url: '/category/clothing',
product_count: 512,
children: [
{ id: 21, name: 'Men', slug: 'men', url: '/category/men', product_count: 234 },
{ id: 22, name: 'Women', slug: 'women', url: '/category/women', product_count: 278 }
]
},
{
id: 3,
name: 'Home & Garden',
slug: 'home-garden',
url: '/category/home-garden',
product_count: 189
}
],
currentCategory: { id: 111, name: 'Headphones', slug: 'headphones' },
demoBreadcrumbs: [
{ label: 'Electronics', url: '/category/electronics' },
{ label: 'Audio', url: '/category/audio' },
{ label: 'Headphones' }
],
// Filter Demo State
demoFilters: {
categories: [
{ id: 'headphones', name: 'Headphones', count: 45 },
{ id: 'earbuds', name: 'Earbuds', count: 32 },
{ id: 'speakers', name: 'Speakers', count: 28 }
],
brands: [
{ id: 'audiotech', name: 'AudioTech', count: 24 },
{ id: 'soundmax', name: 'SoundMax', count: 18 },
{ id: 'beatspro', name: 'BeatsPro', count: 15 },
{ id: 'sonicwave', name: 'SonicWave', count: 12 }
],
priceRange: { min: 0, max: 500 },
attributes: {
color: [
{ value: 'black', label: 'Black', hex: '#1a1a1a', count: 35 },
{ value: 'white', label: 'White', hex: '#ffffff', count: 28 },
{ value: 'silver', label: 'Silver', hex: '#C0C0C0', count: 22 },
{ value: 'blue', label: 'Blue', hex: '#3B82F6', count: 15 }
],
size: [
{ value: 'compact', label: 'Compact', count: 18 },
{ value: 'standard', label: 'Standard', count: 42 },
{ value: 'over-ear', label: 'Over-ear', count: 25 }
]
},
ratings: [
{ value: 5, count: 12 },
{ value: 4, count: 28 },
{ value: 3, count: 15 },
{ value: 2, count: 5 },
{ value: 1, count: 2 }
]
},
demoActiveFilters: {
categories: [],
brands: [],
priceMin: undefined,
priceMax: undefined,
rating: undefined,
attributes: {},
inStock: false
},
demoSortBy: 'relevance',
showMobileSearch: false,
showMobileFilters: false,
showCategoryDrawer: false,
// Search Demo Methods
demoFilterProducts() {
componentsLog.debug('Filtering products with:', this.demoActiveFilters);
if (typeof Utils !== 'undefined' && Utils.showToast) {
Utils.showToast('Filters applied!', 'info');
}
},
demoSortProducts() {
componentsLog.debug('Sorting by:', this.demoSortBy);
if (typeof Utils !== 'undefined' && Utils.showToast) {
Utils.showToast('Sorted by: ' + this.demoSortBy, 'info');
}
},
// Social Proof Demo (Priority 5)
demoRating: 4.5,
demoReviewCount: 127,
demoRatingDistribution: { 5: 68, 4: 35, 3: 15, 2: 6, 3: 3 },
demoUserRating: 0,
demoReviews: [
{
id: 1,
author_name: 'John D.',
author_avatar: null,
rating: 5,
title: 'Best headphones I\'ve ever owned',
content: 'The noise cancellation is incredible. I use these for work calls and they block out everything. Battery life is amazing too. Would highly recommend to anyone looking for premium audio quality.',
verified: true,
created_at: '2025-01-15',
helpful_count: 42,
images: []
},
{
id: 2,
author_name: 'Sarah M.',
author_avatar: null,
rating: 4,
title: 'Great sound, slightly tight fit',
content: 'Sound quality is phenomenal and the build quality feels premium. My only complaint is they feel a bit tight after a few hours of continuous use. Otherwise perfect for music lovers.',
verified: true,
created_at: '2025-01-10',
helpful_count: 18,
images: []
},
{
id: 3,
author_name: 'Mike R.',
author_avatar: null,
rating: 5,
title: 'Worth every penny',
content: 'These headphones are absolutely worth the investment. The sound stage is wide, bass is punchy but not overwhelming, and the noise cancellation is top-notch.',
verified: false,
created_at: '2025-01-05',
helpful_count: 7,
images: []
}
],
demoReviewSort: 'newest',
demoNewReview: {
rating: 0,
title: '',
content: '',
images: []
},
submittingReview: false,
showReviewForm: false,
// Review Demo Methods
demoSubmitReview() {
this.submittingReview = true;
setTimeout(() => {
this.submittingReview = false;
this.showReviewForm = false;
this.demoNewReview = { rating: 0, title: '', content: '', images: [] };
if (typeof Utils !== 'undefined' && Utils.showToast) {
Utils.showToast('Review submitted successfully!', 'success');
}
}, 1500);
},
demoMarkHelpful(reviewId) {
const review = this.demoReviews.find(r => r.id === reviewId);
if (review) {
review.helpful_count++;
}
if (typeof Utils !== 'undefined' && Utils.showToast) {
Utils.showToast('Thanks for your feedback!', 'info');
}
},
// Sample form data for examples
exampleForm: {
textInput: 'Sample text',