feat(ui): Zone 11 - MarketplaceView SaaS polish (glass, glow, motion, fix allProducts, ProductCard tokens)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
c1ce0c4b5a
commit
17234b6222
7 changed files with 64 additions and 39 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Card } from '../ui/card';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Button } from '../ui/button';
|
||||
|
|
@ -21,9 +22,14 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
isPlayingPreview,
|
||||
}) => {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Card
|
||||
variant="default"
|
||||
className="group p-0 overflow-hidden border-transparent hover:bg-white/5 transition-colors duration-200 bg-kodo-graphite cursor-pointer"
|
||||
variant="glass"
|
||||
className="group p-0 overflow-hidden border-white/5 bg-black/20 backdrop-blur-xl hover-glow-cyan transition-all duration-300 cursor-pointer"
|
||||
onClick={() => onClick(product)}
|
||||
>
|
||||
{/* Image & Overlay */}
|
||||
|
|
@ -51,7 +57,7 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
e.stopPropagation();
|
||||
onPreview(product.id);
|
||||
}}
|
||||
className="w-16 h-16 rounded-full bg-kodo-cyan text-black flex items-center justify-center shadow-lg"
|
||||
className="w-16 h-16 rounded-full bg-primary text-primary-foreground flex items-center justify-center shadow-lg"
|
||||
>
|
||||
{isPlayingPreview ? (
|
||||
<Pause className="w-8 h-8 fill-current" />
|
||||
|
|
@ -92,17 +98,17 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-kodo-content-dim text-xs mb-4 flex items-center gap-1">
|
||||
<p className="text-muted-foreground text-xs mb-4 flex items-center gap-1">
|
||||
by{' '}
|
||||
<span className="text-kodo-text-main hover:underline">
|
||||
<span className="text-foreground hover:underline">
|
||||
{product.author}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-2 mb-4 text-xs text-kodo-gold">
|
||||
<div className="flex items-center gap-2 mb-4 text-xs text-warning">
|
||||
<Star className="w-3 h-3 fill-current" />
|
||||
<span className="font-bold">{product.rating}</span>
|
||||
<span className="text-kodo-content-dim">({product.reviewCount || 0})</span>
|
||||
<span className="text-muted-foreground">({product.reviewCount || 0})</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
|
|
@ -120,7 +126,7 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="border border-kodo-steel hover:bg-white/10"
|
||||
className="border border-border hover:bg-white/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick(product);
|
||||
|
|
@ -131,5 +137,6 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export function MarketplaceView({ initialProducts }: MarketplaceViewProps = {})
|
|||
product={selectedProduct}
|
||||
onBack={() => setSelectedProduct(null)}
|
||||
onAddToCart={addToCart}
|
||||
similarProducts={allProducts.filter((p) => p.id !== selectedProduct.id).slice(0, 3)}
|
||||
similarProducts={products.filter((p) => p.id !== selectedProduct.id).slice(0, 3)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function MarketplaceViewCategories({
|
|||
onFiltersToggle,
|
||||
}: MarketplaceViewCategoriesProps) {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4 mb-8 bg-card/50 p-2 rounded-xl border border-border">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4 mb-8 bg-card/50 p-2 rounded-[var(--radius-xl)] border border-border backdrop-blur-sm">
|
||||
<div className="flex items-center gap-2 overflow-x-auto w-full md:w-auto p-1 no-scrollbar">
|
||||
<Button
|
||||
variant={filtersOpen ? 'primary' : 'ghost'}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ProductCard } from '@/components/marketplace/ProductCard';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import type { Product } from '@/types';
|
||||
|
||||
const listVariants = {
|
||||
visible: { transition: { staggerChildren: 0.04, delayChildren: 0.02 } },
|
||||
};
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 12 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
};
|
||||
|
||||
interface MarketplaceViewGridProps {
|
||||
products: Product[];
|
||||
loading: boolean;
|
||||
|
|
@ -39,17 +48,23 @@ export function MarketplaceViewGrid({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-8">
|
||||
<motion.div
|
||||
className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-8"
|
||||
variants={listVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
{products.map((product) => (
|
||||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
onClick={onProductClick}
|
||||
onPreview={() => {}}
|
||||
isPlayingPreview={false}
|
||||
onAddToCart={onAddToCart}
|
||||
/>
|
||||
<motion.div key={product.id} variants={itemVariants}>
|
||||
<ProductCard
|
||||
product={product}
|
||||
onClick={onProductClick}
|
||||
onPreview={() => {}}
|
||||
isPlayingPreview={false}
|
||||
onAddToCart={onAddToCart}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
|
||||
interface MarketplaceViewHeaderProps {
|
||||
|
|
@ -8,22 +9,24 @@ interface MarketplaceViewHeaderProps {
|
|||
|
||||
export function MarketplaceViewHeader({ searchQuery, onSearchChange }: MarketplaceViewHeaderProps) {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row justify-between items-end mb-8 gap-4">
|
||||
<div>
|
||||
<h2 className="text-3xl font-display font-bold text-foreground mb-2 tracking-tight">MARKETPLACE</h2>
|
||||
<p className="text-muted-foreground font-mono text-sm">
|
||||
Discover premium sounds and tools.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 w-full md:w-auto">
|
||||
<div className="relative flex-1 md:w-64">
|
||||
<SearchInput
|
||||
placeholder="Search sounds..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
/>
|
||||
<Card variant="glass" className="mb-8 p-6 border-white/5 bg-black/20 backdrop-blur-xl hover-glow-cyan transition-shadow duration-300">
|
||||
<div className="flex flex-col md:flex-row justify-between items-end gap-4">
|
||||
<div>
|
||||
<h2 className="text-3xl font-display font-bold text-foreground mb-2 tracking-tight">MARKETPLACE</h2>
|
||||
<p className="text-muted-foreground font-mono text-sm">
|
||||
Discover premium sounds and tools.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 w-full md:w-auto">
|
||||
<div className="relative flex-1 md:w-64">
|
||||
<SearchInput
|
||||
placeholder="Search sounds..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { Card } from '@/components/ui/card';
|
|||
export function MarketplaceViewSidebar() {
|
||||
return (
|
||||
<div className="w-64 flex-shrink-0 space-y-8 hidden lg:block animate-slideInLeft">
|
||||
<Card variant="default">
|
||||
<h3 className="text-xs font-bold text-muted-foreground uppercase tracking-widest mb-4">
|
||||
<Card variant="glass" className="border-white/5 bg-black/20 backdrop-blur-xl hover-glow-cyan transition-shadow duration-300">
|
||||
<h3 className="text-xs font-bold text-muted-foreground uppercase tracking-widest mb-4">
|
||||
Price
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function MarketplaceViewSkeleton() {
|
|||
<Skeleton className="h-10 w-full md:w-64" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4 mb-8 bg-card/50 p-2 rounded-xl border border-border">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4 mb-8 bg-card/50 p-2 rounded-[var(--radius-xl)] border border-border">
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Skeleton className="h-9 w-24" />
|
||||
<Skeleton className="h-9 w-16" />
|
||||
|
|
@ -26,7 +26,7 @@ export function MarketplaceViewSkeleton() {
|
|||
<div className="flex-1">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-8">
|
||||
{[1, 2, 3, 4, 5, 6].map((i) => (
|
||||
<Skeleton key={i} className="aspect-square rounded-xl" />
|
||||
<Skeleton key={i} className="aspect-square rounded-[var(--radius-xl)]" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue