feat: add e-commerce section to components showcase page
- Add E-commerce section with live demos for all shop macros - Add demo products and cart state to components.js - Add demo methods: demoAddToCart, demoToggleWishlist, demoRemoveFromCart - Showcase product cards, mini cart, add-to-cart functionality - Add Macros section navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,11 +11,6 @@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js" defer></script>
|
||||
<style>
|
||||
/* Ensure sticky positioning works properly */
|
||||
.sticky {
|
||||
position: -webkit-sticky !important;
|
||||
position: sticky !important;
|
||||
}
|
||||
/* Add smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
@@ -49,48 +44,280 @@ html {
|
||||
</div>
|
||||
|
||||
<!-- Layout: Sidebar + Content -->
|
||||
<div class="flex gap-6 relative">
|
||||
<div class="flex gap-6 items-start">
|
||||
|
||||
<!-- Sticky Navigation Sidebar - FIXED FOR REAL -->
|
||||
<div class="w-64 flex-shrink-0 self-start">
|
||||
<div class="sticky top-24" style="position: -webkit-sticky; position: sticky;">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3">Sections</h3>
|
||||
<nav class="space-y-1">
|
||||
<template x-for="section in sections" :key="section.id">
|
||||
<a
|
||||
:href="'#' + section.id"
|
||||
@click.prevent="goToSection(section.id)"
|
||||
class="flex items-center px-3 py-2 text-sm rounded-lg transition-colors"
|
||||
:class="isSectionActive(section.id)
|
||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-200 font-medium'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
>
|
||||
<span x-html="$icon(section.icon, 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="section.name"></span>
|
||||
</a>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- Sticky Navigation Sidebar -->
|
||||
<aside class="w-64 flex-shrink-0 sticky top-6 max-h-[calc(100vh-3rem)] overflow-y-auto">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3">Sections</h3>
|
||||
<nav class="space-y-1">
|
||||
<template x-for="section in sections" :key="section.id">
|
||||
<a
|
||||
:href="'#' + section.id"
|
||||
@click.prevent="goToSection(section.id)"
|
||||
class="flex items-center px-3 py-2 text-sm rounded-lg transition-colors"
|
||||
:class="isSectionActive(section.id)
|
||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-200 font-medium'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
>
|
||||
<span x-html="$icon(section.icon, 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="section.name"></span>
|
||||
</a>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Quick Tips -->
|
||||
<div class="mt-4 bg-blue-50 dark:bg-gray-800 border border-blue-200 dark:border-gray-700 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('light-bulb', 'w-5 h-5 text-blue-600 dark:text-blue-400 mr-2 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-blue-900 dark:text-blue-200 mb-1">Pro Tip</p>
|
||||
<p class="text-xs text-blue-800 dark:text-blue-300">
|
||||
Click any code block to copy it to your clipboard!
|
||||
</p>
|
||||
</div>
|
||||
<!-- Quick Tips -->
|
||||
<div class="mt-4 bg-blue-50 dark:bg-gray-800 border border-blue-200 dark:border-gray-700 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('light-bulb', 'w-5 h-5 text-blue-600 dark:text-blue-400 mr-2 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-blue-900 dark:text-blue-200 mb-1">Pro Tip</p>
|
||||
<p class="text-xs text-blue-800 dark:text-blue-300">
|
||||
Click any code block to copy it to your clipboard!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content - WIDELY SPACED SECTIONS -->
|
||||
<div class="flex-1 space-y-32">
|
||||
|
||||
<!-- E-COMMERCE SECTION -->
|
||||
<section id="ecommerce" class="scroll-mt-24">
|
||||
<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('shopping-cart', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
E-commerce Components
|
||||
</h2>
|
||||
|
||||
<div class="mb-6 p-4 bg-green-50 dark:bg-gray-700 border border-green-200 dark:border-gray-600 rounded-lg">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('information-circle', 'w-5 h-5 text-green-600 dark:text-green-400 mr-2 flex-shrink-0 mt-0.5')"></span>
|
||||
<div>
|
||||
<p class="text-sm text-green-800 dark:text-gray-200">
|
||||
<strong>Shop Components:</strong> Reusable e-commerce components from <code class="bg-green-100 dark:bg-gray-600 px-1 rounded">shared/macros/shop/</code>. These support vendor theming via CSS variables and are designed for the shop frontend.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Available Shop Macros -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Available Shop Macro Files</h3>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('cube', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
product-card.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Product cards with badges, ratings, wishlist</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">product_card(), product_badge()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('view-grid', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
product-grid.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Responsive product grid with loading states</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">product_grid(), product_skeleton()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('shopping-bag', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
add-to-cart.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Add to cart buttons and forms</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">add_to_cart_button(), add_to_cart_form()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('shopping-cart', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
mini-cart.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Cart dropdown, cart items, summary</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">mini_cart(), cart_item(), cart_summary()</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Card Demo -->
|
||||
<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 Cards</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Versatile product cards with badges, ratings, wishlist, and quick-add functionality. Supports <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">sm</code>, <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">md</code>, and <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">lg</code> sizes.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 mb-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<template x-for="product in demoProducts" :key="product.id">
|
||||
<div class="group relative bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 overflow-hidden max-w-[280px] mx-auto">
|
||||
<div class="relative h-48 overflow-hidden bg-gray-100 dark:bg-gray-700">
|
||||
<a :href="product.url">
|
||||
<img :src="product.image_url" :alt="product.name" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" loading="lazy">
|
||||
</a>
|
||||
<div class="absolute top-2 left-2 flex flex-col gap-1">
|
||||
<span x-show="product.sale_price" class="text-xs px-2 py-1 font-semibold text-white bg-red-500 rounded" x-text="'-' + Math.round((1 - product.sale_price / product.price) * 100) + '%'"></span>
|
||||
<span x-show="product.is_new" class="text-xs px-2 py-1 font-semibold text-white bg-green-500 rounded">New</span>
|
||||
</div>
|
||||
<button type="button" @click.prevent="demoToggleWishlist(product)" class="absolute top-2 right-2 p-1.5 rounded-full bg-white/80 dark:bg-gray-800/80 hover:bg-white dark:hover:bg-gray-800 transition-colors opacity-0 group-hover:opacity-100" :class="product.in_wishlist ? 'text-red-500' : 'text-gray-400 hover:text-red-500'">
|
||||
<span x-html="$icon('heart', 'w-5 h-5')" :class="product.in_wishlist && 'fill-current'"></span>
|
||||
</button>
|
||||
<div x-show="product.stock === 0" class="absolute inset-0 bg-black/50 flex items-center justify-center">
|
||||
<span class="text-white font-semibold text-xs px-2 py-1 bg-gray-900 rounded">Out of Stock</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<h3 class="text-base font-medium text-gray-900 dark:text-gray-100 mb-1 line-clamp-2">
|
||||
<a :href="product.url" class="hover:text-purple-600 dark:hover:text-purple-400" x-text="product.name"></a>
|
||||
</h3>
|
||||
<div class="flex items-center gap-1 mb-2" x-show="product.rating">
|
||||
<div class="flex">
|
||||
<template x-for="i in 5" :key="i">
|
||||
<span x-html="$icon('star', 'w-4 h-4')" :class="i <= Math.round(product.rating) ? 'text-yellow-400 fill-current' : 'text-gray-300 dark:text-gray-600'"></span>
|
||||
</template>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="'(' + product.review_count + ')'"></span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-base font-bold" :class="product.sale_price ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100'" x-text="'€' + (product.sale_price || product.price).toFixed(2)"></span>
|
||||
<span x-show="product.sale_price" class="text-sm text-gray-400 line-through" x-text="'€' + product.price.toFixed(2)"></span>
|
||||
</div>
|
||||
<button type="button" @click="demoAddToCart(product)" :disabled="product.stock === 0 || addingToCart" class="w-full text-sm px-3 py-2 font-medium text-white bg-purple-600 hover:bg-purple-700 dark:bg-purple-500 dark:hover:bg-purple-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2">
|
||||
<span x-show="addingToCart" x-html="$icon('spinner', 'w-5 h-5')"></span>
|
||||
<span x-show="!addingToCart" x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
|
||||
<span x-text="product.stock === 0 ? 'Out of Stock' : 'Add to Cart'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/product-card.html' import product_card %}
|
||||
|
||||
<template x-for="product in products" :key="product.id">
|
||||
{{ product_card() }}
|
||||
</template>
|
||||
|
||||
{# With options #}
|
||||
{{ product_card(size='lg', show_vendor=true, show_wishlist=false) }}{% 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>
|
||||
|
||||
<!-- Mini Cart Demo -->
|
||||
<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">Mini Cart</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Cart icon button with badge and dropdown preview. Click the cart icon to see the dropdown.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 mb-4">
|
||||
<div class="flex justify-center">
|
||||
<div class="relative inline-block">
|
||||
<button type="button" @click="showDemoCart = !showDemoCart" class="relative p-2 text-gray-600 dark:text-gray-300 hover:text-purple-600 dark:hover:text-purple-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||
<span x-html="$icon('shopping-cart', 'w-6 h-6')"></span>
|
||||
<span x-show="demoCart.item_count > 0" x-text="demoCart.item_count" class="absolute w-5 h-5 text-xs -top-1.5 -right-1.5 flex items-center justify-center font-bold text-white bg-purple-600 dark:bg-purple-500 rounded-full"></span>
|
||||
</button>
|
||||
<div x-show="showDemoCart" x-transition @click.away="showDemoCart = false" class="absolute right-0 top-full mt-2 w-80 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 z-50">
|
||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Shopping Cart</h3>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="demoCart.item_count + ' item' + (demoCart.item_count !== 1 ? 's' : '')"></p>
|
||||
</div>
|
||||
<div x-show="demoCart.items.length === 0" class="px-4 py-8 text-center">
|
||||
<div class="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600">
|
||||
<span x-html="$icon('shopping-cart', 'w-full h-full')"></span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Your cart is empty</p>
|
||||
</div>
|
||||
<div x-show="demoCart.items.length > 0" class="max-h-64 overflow-y-auto">
|
||||
<template x-for="item in demoCart.items" :key="item.id">
|
||||
<div class="flex gap-3 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<img :src="item.image_url" :alt="item.name" class="w-14 h-14 object-cover rounded-lg" loading="lazy">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate" x-text="item.name"></h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="item.variant_name"></p>
|
||||
<div class="flex items-center justify-between mt-1">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="'Qty: ' + item.quantity"></span>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100" x-text="'€' + (item.price * item.quantity).toFixed(2)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" @click="demoRemoveFromCart(item.id)" class="flex-shrink-0 p-1 text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors">
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div x-show="demoCart.items.length > 0" class="px-4 py-3 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">Subtotal</span>
|
||||
<span class="text-lg font-bold text-gray-900 dark:text-gray-100" x-text="'€' + demoCart.subtotal.toFixed(2)"></span>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<a href="#" class="block w-full px-4 py-2 text-center text-sm font-medium text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-900/30 hover:bg-purple-100 dark:hover:bg-purple-900/50 rounded-lg transition-colors">View Cart</a>
|
||||
<a href="#" class="block w-full px-4 py-2 text-center text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 dark:bg-purple-500 dark:hover:bg-purple-600 rounded-lg transition-colors">Checkout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/mini-cart.html' import cart_icon_button, mini_cart %}
|
||||
|
||||
<div class="relative">
|
||||
{{ cart_icon_button(cart_count_var='cart.item_count', toggle_action='showCart = !showCart') }}
|
||||
{{ mini_cart(cart_var='cart', show_var='showCart') }}
|
||||
</div>{% 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>
|
||||
|
||||
<!-- Add to Cart Demo -->
|
||||
<div class="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">Add to Cart Button</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Standardized add to cart buttons with loading states. Integrates with <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">number_stepper</code> for quantity selection.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 mb-4">
|
||||
<div class="flex flex-wrap items-end gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Quantity</label>
|
||||
<div class="flex" role="group" aria-label="Quantity">
|
||||
<button type="button" @click="demoQuantity = Math.max(1, demoQuantity - 1)" :disabled="demoQuantity <= 1" class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-r-0 rounded-l-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('minus', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<input type="number" x-model.number="demoQuantity" min="1" max="99" class="no-spinner px-3 py-2 text-sm w-16 text-center text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border-y border-gray-300 dark:border-gray-600 focus:border-purple-400 focus:outline-none" />
|
||||
<button type="button" @click="demoQuantity = Math.min(99, demoQuantity + 1)" :disabled="demoQuantity >= 99" class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-l-0 rounded-r-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('plus', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" @click="demoAddToCart({})" :disabled="addingToCart" class="px-6 py-2.5 text-base font-medium text-white bg-purple-600 hover:bg-purple-700 dark:bg-purple-500 dark:hover:bg-purple-600 rounded-lg transition-colors disabled:opacity-50 flex items-center gap-2">
|
||||
<span x-show="addingToCart" x-html="$icon('spinner', 'w-5 h-5')"></span>
|
||||
<span x-show="!addingToCart" x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
|
||||
<span x-text="addingToCart ? 'Adding...' : 'Add to Cart'"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div x-show="addedToCart" x-transition class="mt-4 flex items-center gap-2 text-green-600 dark:text-green-400 text-sm">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
||||
<span>Added to cart!</span>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/shop/add-to-cart.html' import add_to_cart_button, add_to_cart_form %}
|
||||
|
||||
{# Simple button #}
|
||||
{{ add_to_cart_button(action='addToCart()') }}
|
||||
|
||||
{# Full form with quantity selector #}
|
||||
{{ add_to_cart_form(product_var='product', quantity_var='quantity') }}{% 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>
|
||||
|
||||
<!-- MACROS SECTION -->
|
||||
<section id="macros" class="scroll-mt-24">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
@@ -99,12 +326,12 @@ html {
|
||||
Jinja Macros
|
||||
</h2>
|
||||
|
||||
<div class="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<div class="mb-6 p-4 bg-blue-50 dark:bg-gray-700 border border-blue-200 dark:border-gray-600 rounded-lg">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('information-circle', 'w-5 h-5 text-blue-600 dark:text-blue-400 mr-2 flex-shrink-0 mt-0.5')"></span>
|
||||
<div>
|
||||
<p class="text-sm text-blue-800 dark:text-blue-200">
|
||||
<strong>Reusable Components:</strong> Use Jinja macros from <code class="bg-blue-100 dark:bg-blue-800 px-1 rounded">shared/macros/</code> instead of copy-pasting HTML. This ensures consistency and makes updates easier.
|
||||
<p class="text-sm text-blue-800 dark:text-gray-200">
|
||||
<strong>Reusable Components:</strong> Use Jinja macros from <code class="bg-blue-100 dark:bg-gray-600 px-1 rounded">shared/macros/</code> instead of copy-pasting HTML. This ensures consistency and makes updates easier.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,45 +342,85 @@ html {
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">Available Macro Files</h3>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">pagination.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('dots-horizontal', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
pagination.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Table pagination with page numbers</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">pagination(), pagination_simple()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">alerts.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('exclamation', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
alerts.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Alerts, loading states, toasts</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">loading_state(), error_state(), alert(), toast()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">badges.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('tag', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
badges.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Status badges and indicators</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">badge(), status_badge(), verification_badge(), role_badge()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">buttons.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('cursor-click', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
buttons.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Button variants and action buttons</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">btn_primary(), btn_secondary(), action_button()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">forms.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('clipboard-list', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
forms.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Form inputs with validation</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">form_input(), form_select(), form_textarea()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">tables.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('table', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
tables.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Table components and empty states</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">table_wrapper(), table_header(), table_empty_state()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">cards.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('collection', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
cards.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Stat cards and info cards</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">stat_card(), card(), info_card(), coming_soon_card()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2">headers.html</h4>
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('menu-alt-2', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
headers.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Page headers and breadcrumbs</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">page_header(), section_header(), breadcrumbs()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('view-boards', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
tabs.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Tab navigation components</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">tabs_nav(), tabs_inline(), tab_button()</code>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-800 dark:text-gray-200 mb-2 flex items-center">
|
||||
<span x-html="$icon('adjustments', 'w-4 h-4 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
inputs.html
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Specialized input components</p>
|
||||
<code class="text-xs bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">search_autocomplete(), number_stepper()</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -189,6 +456,71 @@ html {
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Number Stepper Component -->
|
||||
<div class="mt-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">Number Stepper</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
A number input with +/- buttons. Use <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">number_stepper()</code> from <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">inputs.html</code>. Supports size variants and min/max bounds.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<!-- Live Demo -->
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">Small Size</label>
|
||||
<div class="flex" role="group" aria-label="Quantity">
|
||||
<button type="button" @click="demoQuantitySm = Math.max(1, demoQuantitySm - 1)" :disabled="demoQuantitySm <= 1" class="px-2 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-r-0 rounded-l-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('minus', 'w-3 h-3')"></span>
|
||||
</button>
|
||||
<input type="number" x-model.number="demoQuantitySm" min="1" max="10" class="no-spinner px-2 py-1 text-xs w-12 text-center text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border-y border-gray-300 dark:border-gray-600 focus:border-purple-400 focus:outline-none" />
|
||||
<button type="button" @click="demoQuantitySm = Math.min(10, demoQuantitySm + 1)" :disabled="demoQuantitySm >= 10" class="px-2 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-l-0 rounded-r-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('plus', 'w-3 h-3')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">Medium Size (default)</label>
|
||||
<div class="flex" role="group" aria-label="Quantity">
|
||||
<button type="button" @click="demoQuantityMd = Math.max(1, demoQuantityMd - 1)" :disabled="demoQuantityMd <= 1" class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-r-0 rounded-l-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('minus', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<input type="number" x-model.number="demoQuantityMd" min="1" max="99" class="no-spinner px-3 py-2 text-sm w-16 text-center text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border-y border-gray-300 dark:border-gray-600 focus:border-purple-400 focus:outline-none" />
|
||||
<button type="button" @click="demoQuantityMd = Math.min(99, demoQuantityMd + 1)" :disabled="demoQuantityMd >= 99" class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-l-0 rounded-r-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('plus', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">Large Size (step: 100)</label>
|
||||
<div class="flex" role="group" aria-label="Batch Size">
|
||||
<button type="button" @click="demoQuantityLg = Math.max(100, demoQuantityLg - 100)" :disabled="demoQuantityLg <= 100" class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-r-0 rounded-l-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('minus', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
<input type="number" x-model.number="demoQuantityLg" min="100" max="5000" step="100" class="no-spinner px-4 py-3 text-base w-20 text-center text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border-y border-gray-300 dark:border-gray-600 focus:border-purple-400 focus:outline-none" />
|
||||
<button type="button" @click="demoQuantityLg = Math.min(5000, demoQuantityLg + 100)" :disabled="demoQuantityLg >= 5000" class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-600 border-l-0 rounded-r-md hover:bg-gray-200 dark:hover:bg-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||
<span x-html="$icon('plus', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/inputs.html' import number_stepper %}
|
||||
|
||||
{# Basic usage - cart quantity #}
|
||||
{{ number_stepper(model='quantity', min=1, max=99) }}
|
||||
|
||||
{# Small size - compact for tables #}
|
||||
{{ number_stepper(model='item.qty', min=1, max='item.stock', size='sm') }}
|
||||
|
||||
{# Large size with custom step #}
|
||||
{{ number_stepper(model='batchSize', min=100, max=5000, step=100, size='lg', label='Batch Size') }}
|
||||
|
||||
{# With disabled state #}
|
||||
{{ number_stepper(model='qty', min=1, disabled_var='isLoading') }}{% 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>
|
||||
|
||||
@@ -301,6 +633,198 @@ goToPage(n) { if (n !== '...' && n >= 1 && n <= this.totalPages) { this.paginati
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- TABS SECTION -->
|
||||
<section id="tabs" class="scroll-mt-24">
|
||||
<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('view-boards', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
Tabs
|
||||
</h2>
|
||||
|
||||
<div class="mb-6 p-4 bg-blue-50 dark:bg-gray-700 border border-blue-200 dark:border-gray-600 rounded-lg">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('information-circle', 'w-5 h-5 text-blue-600 dark:text-blue-400 mr-2 flex-shrink-0 mt-0.5')"></span>
|
||||
<div>
|
||||
<p class="text-sm text-blue-800 dark:text-gray-200">
|
||||
<strong>Jinja Macros Available:</strong> Use the <code class="bg-blue-100 dark:bg-gray-600 px-1 rounded">tabs_nav</code>, <code class="bg-blue-100 dark:bg-gray-600 px-1 rounded">tabs_inline</code>, and <code class="bg-blue-100 dark:bg-gray-600 px-1 rounded">tab_button</code> macros from <code class="bg-blue-100 dark:bg-gray-600 px-1 rounded">shared/macros/tabs.html</code>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Tabs -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Navigation Tabs (with icons)</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Standalone tabs for page-level navigation. Use <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">tabs_nav()</code> with <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">tab_button()</code>.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<!-- Live Demo -->
|
||||
<div class="mb-6">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||
<nav class="-mb-px flex space-x-8">
|
||||
<button
|
||||
@click="demoActiveTab = 'tab1'"
|
||||
:class="demoActiveTab === 'tab1' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'"
|
||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors"
|
||||
>
|
||||
<span x-html="$icon('home', 'inline w-5 h-5 mr-2')"></span>
|
||||
Dashboard
|
||||
</button>
|
||||
<button
|
||||
@click="demoActiveTab = 'tab2'"
|
||||
:class="demoActiveTab === 'tab2' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'"
|
||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors"
|
||||
>
|
||||
<span x-html="$icon('cog', 'inline w-5 h-5 mr-2')"></span>
|
||||
Settings
|
||||
</button>
|
||||
<button
|
||||
@click="demoActiveTab = 'tab3'"
|
||||
:class="demoActiveTab === 'tab3' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'"
|
||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors"
|
||||
>
|
||||
<span x-html="$icon('user', 'inline w-5 h-5 mr-2')"></span>
|
||||
Profile
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<div x-show="demoActiveTab === 'tab1'">
|
||||
<p class="text-gray-600 dark:text-gray-400">Dashboard content goes here...</p>
|
||||
</div>
|
||||
<div x-show="demoActiveTab === 'tab2'">
|
||||
<p class="text-gray-600 dark:text-gray-400">Settings content goes here...</p>
|
||||
</div>
|
||||
<div x-show="demoActiveTab === 'tab3'">
|
||||
<p class="text-gray-600 dark:text-gray-400">Profile content goes here...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/tabs.html' import tabs_nav, tab_button %}
|
||||
|
||||
{# Navigation tabs with icons #}
|
||||
{% 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>
|
||||
<div x-show="activeTab === 'settings'" x-transition>
|
||||
Settings content...
|
||||
</div>{% 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>
|
||||
|
||||
<!-- Inline Tabs with Counts -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Inline Tabs (with count badges)</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Compact tabs for use inside cards or alongside other elements. Use <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">tabs_inline()</code>.
|
||||
</p>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<!-- Live Demo -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div class="flex space-x-2 border-b border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
@click="demoActiveTab = 'all'"
|
||||
:class="demoActiveTab === 'all' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'"
|
||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors"
|
||||
>
|
||||
All Items
|
||||
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700">42</span>
|
||||
</button>
|
||||
<button
|
||||
@click="demoActiveTab = 'active'"
|
||||
:class="demoActiveTab === 'active' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'"
|
||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors"
|
||||
>
|
||||
Active
|
||||
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700">28</span>
|
||||
</button>
|
||||
<button
|
||||
@click="demoActiveTab = 'archived'"
|
||||
:class="demoActiveTab === 'archived' ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'"
|
||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors"
|
||||
>
|
||||
Archived
|
||||
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700">14</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Search or filters here...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/tabs.html' import tabs_inline, tab_button %}
|
||||
|
||||
{# Inline tabs with count badges - inside a card #}
|
||||
<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 %}
|
||||
|
||||
{# Search or other content alongside #}
|
||||
<div>Search...</div>
|
||||
</div>{% 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>
|
||||
|
||||
<!-- Tabs with Custom Click Handlers -->
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Tabs with Custom Click Handlers</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
When you need to run additional logic on tab change (e.g., load data), use the <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">onclick</code> parameter.
|
||||
</p>
|
||||
<div class="bg-gray-900 rounded-lg p-4 mb-3 overflow-x-auto">
|
||||
<pre class="text-sm text-gray-100"><code>{% raw %}{% from 'shared/macros/tabs.html' import tabs_nav, tab_button %}
|
||||
|
||||
{# Tabs with custom click handlers and different tab variable #}
|
||||
{% 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 %}{% endraw %}</code></pre>
|
||||
</div>
|
||||
<button @click="copyCode(`{% raw %}{% from 'shared/macros/tabs.html' import tabs_nav, tab_button %}
|
||||
|
||||
{% 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 %}{% 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>
|
||||
|
||||
<!-- FORMS SECTION -->
|
||||
<section id="forms" class="scroll-mt-24">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
|
||||
@@ -21,8 +21,10 @@ function adminComponents() {
|
||||
|
||||
// Component sections
|
||||
sections: [
|
||||
{ id: 'ecommerce', name: 'E-commerce', icon: 'shopping-cart' },
|
||||
{ id: 'macros', name: 'Macros', icon: 'template' },
|
||||
{ id: 'pagination', name: 'Pagination', icon: 'dots-horizontal' },
|
||||
{ id: 'tabs', name: 'Tabs', icon: 'view-boards' },
|
||||
{ id: 'forms', name: 'Forms', icon: 'clipboard-list' },
|
||||
{ id: 'buttons', name: 'Buttons', icon: 'cursor-click' },
|
||||
{ id: 'cards', name: 'Cards', icon: 'collection' },
|
||||
@@ -33,6 +35,120 @@ function adminComponents() {
|
||||
{ id: 'charts', name: 'Charts', icon: 'chart-pie' }
|
||||
],
|
||||
|
||||
// Tab demo state
|
||||
demoActiveTab: 'tab1',
|
||||
|
||||
// Number stepper demo state
|
||||
demoQuantitySm: 3,
|
||||
demoQuantityMd: 5,
|
||||
demoQuantityLg: 500,
|
||||
|
||||
// E-commerce demo state
|
||||
demoProducts: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Premium Wireless Headphones',
|
||||
url: '#',
|
||||
image_url: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300&h=300&fit=crop',
|
||||
price: 149.99,
|
||||
sale_price: 119.99,
|
||||
rating: 4.5,
|
||||
review_count: 127,
|
||||
stock: 15,
|
||||
is_new: false,
|
||||
in_wishlist: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Smart Watch Pro',
|
||||
url: '#',
|
||||
image_url: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=300&h=300&fit=crop',
|
||||
price: 299.99,
|
||||
sale_price: null,
|
||||
rating: 4.8,
|
||||
review_count: 89,
|
||||
stock: 8,
|
||||
is_new: true,
|
||||
in_wishlist: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Portable Bluetooth Speaker',
|
||||
url: '#',
|
||||
image_url: 'https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?w=300&h=300&fit=crop',
|
||||
price: 79.99,
|
||||
sale_price: null,
|
||||
rating: 4.2,
|
||||
review_count: 45,
|
||||
stock: 0,
|
||||
is_new: false,
|
||||
in_wishlist: false
|
||||
}
|
||||
],
|
||||
demoCart: {
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
name: 'Premium Wireless Headphones',
|
||||
url: '#',
|
||||
image_url: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300&h=300&fit=crop',
|
||||
price: 119.99,
|
||||
quantity: 1,
|
||||
variant_name: 'Black',
|
||||
max_quantity: 15
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
product_id: 2,
|
||||
name: 'Smart Watch Pro',
|
||||
url: '#',
|
||||
image_url: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=300&h=300&fit=crop',
|
||||
price: 299.99,
|
||||
quantity: 2,
|
||||
variant_name: 'Silver',
|
||||
max_quantity: 8
|
||||
}
|
||||
],
|
||||
item_count: 3,
|
||||
subtotal: 719.97,
|
||||
discount: 0,
|
||||
shipping: 0,
|
||||
tax: 0,
|
||||
total: 719.97,
|
||||
promo_code: null
|
||||
},
|
||||
showDemoCart: false,
|
||||
demoQuantity: 1,
|
||||
addingToCart: false,
|
||||
addedToCart: false,
|
||||
|
||||
// E-commerce demo methods
|
||||
demoAddToCart(product) {
|
||||
this.addingToCart = true;
|
||||
setTimeout(() => {
|
||||
this.addingToCart = false;
|
||||
this.addedToCart = true;
|
||||
setTimeout(() => { this.addedToCart = false; }, 2000);
|
||||
if (typeof Utils !== 'undefined' && Utils.showToast) {
|
||||
Utils.showToast('Added to cart!', 'success');
|
||||
}
|
||||
}, 800);
|
||||
},
|
||||
demoToggleWishlist(product) {
|
||||
product.in_wishlist = !product.in_wishlist;
|
||||
const action = product.in_wishlist ? 'Added to' : 'Removed from';
|
||||
if (typeof Utils !== 'undefined' && Utils.showToast) {
|
||||
Utils.showToast(`${action} wishlist`, 'success');
|
||||
}
|
||||
},
|
||||
demoRemoveFromCart(itemId) {
|
||||
this.demoCart.items = this.demoCart.items.filter(i => i.id !== itemId);
|
||||
this.demoCart.item_count = this.demoCart.items.reduce((sum, i) => sum + i.quantity, 0);
|
||||
this.demoCart.subtotal = this.demoCart.items.reduce((sum, i) => sum + (i.price * i.quantity), 0);
|
||||
this.demoCart.total = this.demoCart.subtotal;
|
||||
},
|
||||
|
||||
// Sample form data for examples
|
||||
exampleForm: {
|
||||
textInput: 'Sample text',
|
||||
|
||||
Reference in New Issue
Block a user