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:
senke 2026-02-07 16:44:13 +01:00
parent c1ce0c4b5a
commit 17234b6222
7 changed files with 64 additions and 39 deletions

View file

@ -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>
);
};

View file

@ -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)}
/>
);
}

View file

@ -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'}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

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

View file

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