feat: add Priority 3 demos to components showcase page
Add live demos for: - Product Gallery with image navigation and thumbnails - Variant Selector with size buttons and color swatches - Product Info with title, price, rating, stock status - Product Tabs with description, specifications, reviews Add demoProductDetail state with full product data including images, sizes, colors, specifications, and sample reviews. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -315,6 +315,310 @@ html {
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Product Gallery Demo (Priority 3) -->
|
||||
<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">Product Gallery</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Image gallery with thumbnails, navigation, zoom on hover, and fullscreen lightbox.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 mb-4">
|
||||
<div class="max-w-lg mx-auto">
|
||||
<div class="space-y-4">
|
||||
{# Main Image #}
|
||||
<div class="relative group">
|
||||
<div class="relative aspect-square bg-gray-100 dark:bg-gray-700 rounded-lg overflow-hidden cursor-crosshair">
|
||||
<img
|
||||
:src="demoProductDetail.images[selectedImage]?.url"
|
||||
:alt="demoProductDetail.images[selectedImage]?.alt"
|
||||
class="absolute inset-0 w-full h-full object-contain"
|
||||
/>
|
||||
<div class="absolute bottom-3 left-3 bg-black/50 text-white text-xs px-2 py-1 rounded">
|
||||
<span x-text="(selectedImage + 1) + ' / ' + demoProductDetail.images.length"></span>
|
||||
</div>
|
||||
</div>
|
||||
{# Navigation Arrows #}
|
||||
<button
|
||||
type="button"
|
||||
@click="selectedImage = selectedImage > 0 ? selectedImage - 1 : demoProductDetail.images.length - 1"
|
||||
class="absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-white/80 dark:bg-gray-800/80 rounded-full shadow-md opacity-0 group-hover:opacity-100 transition-opacity hover:bg-white dark:hover:bg-gray-800"
|
||||
>
|
||||
<span x-html="$icon('chevron-left', 'w-5 h-5 text-gray-700 dark:text-gray-300')"></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="selectedImage = selectedImage < demoProductDetail.images.length - 1 ? selectedImage + 1 : 0"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-white/80 dark:bg-gray-800/80 rounded-full shadow-md opacity-0 group-hover:opacity-100 transition-opacity hover:bg-white dark:hover:bg-gray-800"
|
||||
>
|
||||
<span x-html="$icon('chevron-right', 'w-5 h-5 text-gray-700 dark:text-gray-300')"></span>
|
||||
</button>
|
||||
</div>
|
||||
{# Thumbnails #}
|
||||
<div class="flex gap-2 overflow-x-auto pb-2">
|
||||
<template x-for="(image, index) in demoProductDetail.images" :key="image.id">
|
||||
<button
|
||||
type="button"
|
||||
@click="selectedImage = index"
|
||||
class="flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 transition-all"
|
||||
:class="selectedImage === index
|
||||
? 'border-purple-500 dark:border-purple-400 ring-2 ring-purple-500/30'
|
||||
: 'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500'"
|
||||
>
|
||||
<img :src="image.url" :alt="image.alt" class="w-full h-full object-cover" />
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/product-gallery.html' import product_gallery %}
|
||||
|
||||
{{ product_gallery(images_var='product.images', enable_zoom=true, enable_lightbox=true) }}{% endraw %}`)" class="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>
|
||||
|
||||
<!-- Variant Selector Demo (Priority 3) -->
|
||||
<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">Variant Selector</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Product variant selection with button groups and color swatches. Supports out-of-stock indicators.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 mb-4">
|
||||
<div class="max-w-md mx-auto space-y-6">
|
||||
{# Size Selector #}
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Size</label>
|
||||
<span x-show="selectedSize" class="text-sm text-gray-600 dark:text-gray-400" x-text="selectedSize?.name"></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<template x-for="size in demoProductDetail.sizes" :key="size.id">
|
||||
<button
|
||||
type="button"
|
||||
@click="selectedSize = size"
|
||||
:disabled="size.stock === 0"
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg border-2 transition-all"
|
||||
:class="{
|
||||
'border-purple-500 dark:border-purple-400 bg-purple-50 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300': selectedSize?.id === size.id,
|
||||
'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500 text-gray-700 dark:text-gray-300': selectedSize?.id !== size.id && size.stock > 0,
|
||||
'border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-400 dark:text-gray-500 cursor-not-allowed line-through': size.stock === 0
|
||||
}"
|
||||
>
|
||||
<span x-text="size.name"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
{# Color Swatches #}
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Color</label>
|
||||
<span x-show="selectedColor" class="text-sm text-gray-600 dark:text-gray-400" x-text="selectedColor?.name"></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<template x-for="color in demoProductDetail.colors" :key="color.id">
|
||||
<button
|
||||
type="button"
|
||||
@click="selectedColor = color"
|
||||
:disabled="color.stock === 0"
|
||||
:title="color.name + (color.stock === 0 ? ' - Out of stock' : '')"
|
||||
class="relative w-10 h-10 rounded-full border-2 transition-all shadow-sm"
|
||||
:class="{
|
||||
'ring-2 ring-offset-2 ring-purple-500 dark:ring-offset-gray-800 border-gray-300': selectedColor?.id === color.id,
|
||||
'border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500': selectedColor?.id !== color.id && color.stock > 0,
|
||||
'opacity-40 cursor-not-allowed': color.stock === 0
|
||||
}"
|
||||
:style="'background-color: ' + color.color_hex"
|
||||
>
|
||||
<span x-show="color.stock === 0" class="absolute inset-0 flex items-center justify-center">
|
||||
<span class="w-full h-0.5 bg-gray-600 rotate-45 absolute"></span>
|
||||
</span>
|
||||
<span x-show="selectedColor?.id === color.id" class="absolute inset-0 flex items-center justify-center">
|
||||
<span x-html="$icon('check', 'w-5 h-5 text-white')"></span>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/variant-selector.html' import size_selector, color_swatches %}
|
||||
|
||||
{{ size_selector(sizes_var='product.sizes', selected_var='selectedSize') }}
|
||||
|
||||
{{ color_swatches(colors_var='product.colors', selected_var='selectedColor') }}{% endraw %}`)" class="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>
|
||||
|
||||
<!-- Product Info Demo (Priority 3) -->
|
||||
<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">Product Info</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Product details section with title, price, rating, stock status, and description.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 mb-4">
|
||||
<div class="max-w-lg mx-auto space-y-4">
|
||||
{# Category / Vendor #}
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<a :href="demoProductDetail.category.url" class="hover:text-purple-600 dark:hover:text-purple-400" x-text="demoProductDetail.category.name"></a>
|
||||
<span>•</span>
|
||||
<span>Sold by <a :href="demoProductDetail.vendor.url" class="font-medium hover:text-purple-600 dark:hover:text-purple-400" x-text="demoProductDetail.vendor.name"></a></span>
|
||||
</div>
|
||||
{# Title #}
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white" x-text="demoProductDetail.name"></h1>
|
||||
{# Rating #}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center">
|
||||
<template x-for="i in 5" :key="i">
|
||||
<span x-html="$icon('star', 'w-5 h-5')" :class="i <= Math.round(demoProductDetail.rating) ? 'text-yellow-400 fill-current' : 'text-gray-300 dark:text-gray-600'"></span>
|
||||
</template>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="demoProductDetail.rating.toFixed(1)"></span>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">(<span x-text="demoProductDetail.review_count"></span> reviews)</span>
|
||||
</div>
|
||||
{# Price #}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-2xl font-bold text-red-600 dark:text-red-400" x-text="'$' + demoProductDetail.sale_price.toFixed(2)"></span>
|
||||
<span class="text-lg text-gray-500 dark:text-gray-400 line-through" x-text="'$' + demoProductDetail.price.toFixed(2)"></span>
|
||||
<span class="px-2 py-1 text-sm bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 rounded-full font-medium" x-text="'-' + Math.round((1 - demoProductDetail.sale_price / demoProductDetail.price) * 100) + '%'"></span>
|
||||
</div>
|
||||
{# Description #}
|
||||
<p class="text-gray-600 dark:text-gray-400" x-text="demoProductDetail.short_description"></p>
|
||||
{# Stock Status #}
|
||||
<div class="flex items-center gap-1.5 text-green-600 dark:text-green-400">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
||||
<span class="text-sm font-medium">In Stock</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/product-info.html' import product_info, product_price, product_rating %}
|
||||
|
||||
{{ product_info(product_var='product', show_vendor=true, show_rating=true) }}
|
||||
|
||||
{# Or use individual components #}
|
||||
{{ product_price(product_var='product', size='lg') }}
|
||||
{{ product_rating(product_var='product', clickable=true) }}{% endraw %}`)" class="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>
|
||||
|
||||
<!-- Product Tabs Demo (Priority 3) -->
|
||||
<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">Product Tabs</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Tabbed content for product description, specifications, reviews, and shipping info.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 mb-4">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
{# Tab Navigation #}
|
||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||
<nav class="flex gap-4 -mb-px">
|
||||
<button type="button" @click="activeProductTab = 'description'" class="flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors" :class="activeProductTab === 'description' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400'">
|
||||
<span x-html="$icon('document-text', 'w-5 h-5')"></span>
|
||||
Description
|
||||
</button>
|
||||
<button type="button" @click="activeProductTab = 'specifications'" class="flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors" :class="activeProductTab === 'specifications' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400'">
|
||||
<span x-html="$icon('clipboard-list', 'w-5 h-5')"></span>
|
||||
Specifications
|
||||
</button>
|
||||
<button type="button" @click="activeProductTab = 'reviews'" class="flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors" :class="activeProductTab === 'reviews' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400'">
|
||||
<span x-html="$icon('star', 'w-5 h-5')"></span>
|
||||
Reviews
|
||||
<span class="px-2 py-0.5 text-xs rounded-full" :class="activeProductTab === 'reviews' ? 'bg-purple-100 dark:bg-purple-900/30 text-purple-600' : 'bg-gray-100 dark:bg-gray-700 text-gray-600'" x-text="demoProductDetail.review_count"></span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
{# Tab Content #}
|
||||
<div class="py-6">
|
||||
{# Description Tab #}
|
||||
<div x-show="activeProductTab === 'description'" x-transition>
|
||||
<div class="prose prose-gray dark:prose-invert max-w-none" x-html="demoProductDetail.description"></div>
|
||||
<div class="mt-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Key Features</h3>
|
||||
<ul class="space-y-2">
|
||||
<template x-for="feature in demoProductDetail.features" :key="feature">
|
||||
<li class="flex items-start gap-2">
|
||||
<span x-html="$icon('check', 'w-5 h-5 text-green-500 flex-shrink-0 mt-0.5')"></span>
|
||||
<span class="text-gray-600 dark:text-gray-400" x-text="feature"></span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{# Specifications Tab #}
|
||||
<div x-show="activeProductTab === 'specifications'" x-transition>
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<template x-for="(spec, index) in demoProductDetail.specifications" :key="spec.name">
|
||||
<tr :class="index % 2 === 0 ? 'bg-gray-50 dark:bg-gray-800/50' : 'bg-white dark:bg-gray-800'">
|
||||
<td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white w-1/3" x-text="spec.name"></td>
|
||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400" x-text="spec.value"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{# Reviews Tab #}
|
||||
<div x-show="activeProductTab === 'reviews'" x-transition>
|
||||
<div class="flex flex-col md:flex-row gap-8 mb-8 pb-8 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="text-center md:text-left">
|
||||
<div class="text-5xl font-bold text-gray-900 dark:text-white" x-text="demoProductDetail.rating.toFixed(1)"></div>
|
||||
<div class="flex items-center justify-center md:justify-start gap-1 mt-2">
|
||||
<template x-for="i in 5" :key="i">
|
||||
<span x-html="$icon('star', 'w-5 h-5')" :class="i <= Math.round(demoProductDetail.rating) ? 'text-yellow-400 fill-current' : 'text-gray-300 dark:text-gray-600'"></span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-gray-600 dark:text-gray-400" x-text="demoProductDetail.review_count + ' reviews'"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<template x-for="review in demoProductDetail.reviews" :key="review.id">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 pb-6 last:border-0">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center">
|
||||
<span class="text-sm font-medium text-purple-600 dark:text-purple-400" x-text="review.author_name.charAt(0)"></span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-gray-900 dark:text-white" x-text="review.author_name"></span>
|
||||
<span x-show="review.verified" class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-green-700 dark:text-green-400 bg-green-100 dark:bg-green-900/30 rounded">
|
||||
<span x-html="$icon('badge-check', 'w-3 h-3')"></span>
|
||||
Verified
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400" x-text="review.created_at"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<template x-for="i in 5" :key="i">
|
||||
<span x-html="$icon('star', 'w-4 h-4')" :class="i <= review.rating ? 'text-yellow-400 fill-current' : 'text-gray-300 dark:text-gray-600'"></span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="font-medium text-gray-900 dark:text-white mb-2" x-text="review.title"></h4>
|
||||
<p class="text-gray-600 dark:text-gray-400" x-text="review.content"></p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/product-tabs.html' import product_tabs %}
|
||||
|
||||
{{ product_tabs(product_var='product', tabs=['description', 'specifications', 'reviews', 'shipping']) }}{% endraw %}`)" class="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>
|
||||
|
||||
|
||||
@@ -149,6 +149,80 @@ function adminComponents() {
|
||||
this.demoCart.total = this.demoCart.subtotal;
|
||||
},
|
||||
|
||||
// Product Detail Demo (Priority 3)
|
||||
demoProductDetail: {
|
||||
id: 1,
|
||||
name: 'Premium Wireless Headphones',
|
||||
sku: 'WH-1000XM5',
|
||||
price: 349.99,
|
||||
sale_price: 279.99,
|
||||
rating: 4.7,
|
||||
review_count: 1247,
|
||||
stock: 23,
|
||||
is_new: true,
|
||||
short_description: 'Industry-leading noise cancellation with exceptional sound quality. 30-hour battery life.',
|
||||
description: '<p>Experience the next level of silence with our Premium Wireless Headphones. Industry-leading noise cancellation technology lets you focus on what matters most.</p><p>With up to 30 hours of battery life, you can enjoy your favorite music all day long. The soft, pressure-relieving ear pads provide all-day comfort.</p>',
|
||||
features: [
|
||||
'Industry-leading noise cancellation',
|
||||
'30-hour battery life',
|
||||
'Hi-Res Audio certified',
|
||||
'Multipoint connection',
|
||||
'Speak-to-chat technology'
|
||||
],
|
||||
specifications: [
|
||||
{ name: 'Driver Size', value: '40mm' },
|
||||
{ name: 'Frequency Response', value: '4Hz - 40,000Hz' },
|
||||
{ name: 'Battery Life', value: '30 hours' },
|
||||
{ name: 'Charging Time', value: '3 hours' },
|
||||
{ name: 'Weight', value: '250g' },
|
||||
{ name: 'Connectivity', value: 'Bluetooth 5.2, 3.5mm' }
|
||||
],
|
||||
images: [
|
||||
{ id: 1, url: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800&h=800&fit=crop', alt: 'Headphones front view' },
|
||||
{ id: 2, url: 'https://images.unsplash.com/photo-1484704849700-f032a568e944?w=800&h=800&fit=crop', alt: 'Headphones side view' },
|
||||
{ id: 3, url: 'https://images.unsplash.com/photo-1583394838336-acd977736f90?w=800&h=800&fit=crop', alt: 'Headphones with case' },
|
||||
{ id: 4, url: 'https://images.unsplash.com/photo-1546435770-a3e426bf472b?w=800&h=800&fit=crop', alt: 'Person wearing headphones' }
|
||||
],
|
||||
sizes: [
|
||||
{ id: 1, name: 'Standard', value: 'standard', stock: 23 },
|
||||
{ id: 2, name: 'Compact', value: 'compact', stock: 8 }
|
||||
],
|
||||
colors: [
|
||||
{ id: 1, name: 'Black', value: 'black', color_hex: '#1a1a1a', stock: 15 },
|
||||
{ id: 2, name: 'Silver', value: 'silver', color_hex: '#C0C0C0', stock: 8 },
|
||||
{ id: 3, name: 'Midnight Blue', value: 'blue', color_hex: '#191970', stock: 0 }
|
||||
],
|
||||
reviews: [
|
||||
{
|
||||
id: 1,
|
||||
author_name: 'John D.',
|
||||
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.',
|
||||
verified: true,
|
||||
created_at: '2025-01-15',
|
||||
helpful_count: 42
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
author_name: 'Sarah M.',
|
||||
rating: 4,
|
||||
title: 'Great sound, slightly tight fit',
|
||||
content: 'Sound quality is phenomenal. My only complaint is they feel a bit tight after a few hours. Otherwise perfect.',
|
||||
verified: true,
|
||||
created_at: '2025-01-10',
|
||||
helpful_count: 18
|
||||
}
|
||||
],
|
||||
rating_distribution: { 5: 68, 4: 22, 3: 6, 2: 3, 1: 1 },
|
||||
category: { name: 'Headphones', slug: 'headphones', url: '/category/headphones' },
|
||||
vendor: { name: 'AudioTech', slug: 'audiotech', url: '/vendor/audiotech' }
|
||||
},
|
||||
selectedImage: 0,
|
||||
selectedSize: null,
|
||||
selectedColor: null,
|
||||
activeProductTab: 'description',
|
||||
|
||||
// Sample form data for examples
|
||||
exampleForm: {
|
||||
textInput: 'Sample text',
|
||||
|
||||
Reference in New Issue
Block a user