feat: integrate Tailwind CSS with custom theme and animations
Added Tailwind CSS and tw-animate-css imports to globals.css. Introduced a custom dark variant and an extensive @theme inline block with CSS variables for colors, radii, and animations. Included keyframes for shimmer-slide, spin-around, gradient, shine, and background-position-spin to enable consistent theming and dynamic UI effects across the application.
This commit is contained in:
1 parent
55c169bd6e
commit
5c1ccbc69a
67 files changed
+8984
-160
No files matched your search
@@ -0,0 +1,186 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ItemCard } from "@/components/item-card";
|
||||
import { MarketplaceFilters } from "@/components/marketplace-filters";
|
||||
import { featuredItems } from "@/lib/mock-data";
|
||||
import { BlurFade } from "@/components/ui/blur-fade";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export function FeaturedItems() {
|
||||
const t = useTranslations("home.featured");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [filters, setFilters] = useState<{
|
||||
rarity: string[];
|
||||
category: string[];
|
||||
priceRange: { min: number; max: number };
|
||||
}>({
|
||||
rarity: [],
|
||||
category: [],
|
||||
priceRange: { min: 0, max: 100000 },
|
||||
});
|
||||
const [sortBy, setSortBy] = useState("name");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Filtrar e ordenar itens
|
||||
const filteredItems = useMemo(() => {
|
||||
let filtered = [...featuredItems];
|
||||
|
||||
// Filtro por busca
|
||||
if (searchQuery) {
|
||||
filtered = filtered.filter((item) =>
|
||||
item.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// Filtro por raridade
|
||||
if (filters.rarity.length > 0) {
|
||||
filtered = filtered.filter((item) =>
|
||||
filters.rarity.includes(item.rarity)
|
||||
);
|
||||
}
|
||||
|
||||
// Filtro por categoria
|
||||
if (filters.category.length > 0) {
|
||||
filtered = filtered.filter((item) =>
|
||||
filters.category.includes(item.category)
|
||||
);
|
||||
}
|
||||
|
||||
// Filtro por faixa de preço
|
||||
filtered = filtered.filter(
|
||||
(item) =>
|
||||
item.price.buy >= filters.priceRange.min &&
|
||||
item.price.buy <= filters.priceRange.max
|
||||
);
|
||||
|
||||
// Ordenação
|
||||
filtered.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case "name":
|
||||
return a.name.localeCompare(b.name);
|
||||
case "price-asc":
|
||||
return a.price.buy - b.price.buy;
|
||||
case "price-desc":
|
||||
return b.price.buy - a.price.buy;
|
||||
case "rarity":
|
||||
const rarityOrder = {
|
||||
common: 0,
|
||||
uncommon: 1,
|
||||
rare: 2,
|
||||
epic: 3,
|
||||
legendary: 4,
|
||||
};
|
||||
return rarityOrder[a.rarity] - rarityOrder[b.rarity];
|
||||
case "trending":
|
||||
return b.priceHistory.highest - a.priceHistory.highest;
|
||||
case "recent":
|
||||
// Simular ordenação por data (mais recente primeiro)
|
||||
return Math.random() - 0.5;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}, [searchQuery, filters, sortBy]);
|
||||
|
||||
const handleSearch = (query: string) => {
|
||||
setSearchQuery(query);
|
||||
};
|
||||
|
||||
const handleFilter = (newFilters: any) => {
|
||||
setFilters(newFilters);
|
||||
};
|
||||
|
||||
const handleSort = (sortValue: string) => {
|
||||
setSortBy(sortValue);
|
||||
};
|
||||
|
||||
// Skeleton para loading
|
||||
const LoadingSkeleton = () => (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{Array.from({ length: 8 }).map((_, index) => (
|
||||
<div key={index} className="space-y-4">
|
||||
<Skeleton className="h-48 w-full rounded-xl" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-6 w-3/4" />
|
||||
<Skeleton className="h-4 w-1/2" />
|
||||
<Skeleton className="h-4 w-1/3" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-2/3" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-muted/30">
|
||||
<div className="container">
|
||||
<BlurFade delay={0.2}>
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold mb-4">{t("title")}</h2>
|
||||
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
|
||||
Descubra os melhores itens do servidor com preços atualizados em
|
||||
tempo real
|
||||
</p>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* Filtros e busca */}
|
||||
<div className="mb-12">
|
||||
<MarketplaceFilters
|
||||
onSearch={handleSearch}
|
||||
onFilter={handleFilter}
|
||||
onSort={handleSort}
|
||||
totalItems={filteredItems.length}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Grid de itens */}
|
||||
{isLoading ? (
|
||||
<LoadingSkeleton />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{filteredItems.map((item, index) => (
|
||||
<BlurFade key={item.id} delay={0.2 + index * 0.05}>
|
||||
<ItemCard item={item} />
|
||||
</BlurFade>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mensagem quando não há resultados */}
|
||||
{filteredItems.length === 0 && !isLoading && (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-6xl mb-4">🔍</div>
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
Nenhum item encontrado
|
||||
</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Tente ajustar os filtros ou fazer uma nova busca
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchQuery("");
|
||||
setFilters({
|
||||
rarity: [],
|
||||
category: [],
|
||||
priceRange: { min: 0, max: 100000 },
|
||||
});
|
||||
setSortBy("name");
|
||||
}}
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Limpar todos os filtros
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user