Skip to content

Commit 4cd8cdb

Browse files
committed
Fixe App + responsive menu
1 parent 41b4313 commit 4cd8cdb

1 file changed

Lines changed: 57 additions & 34 deletions

File tree

Items/src/App.jsx

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { useState, useMemo, useEffect, useRef } from 'react';
22
import { items } from './constants/items';
33

4-
const ITEMS_PER_PAGE = 20;
4+
const ITEMS_PER_PAGE = 24;
55

66
export default function App() {
77
const [search, setSearch] = useState('');
88
const [activeCategory, setActiveCategory] = useState('all');
99
const [page, setPage] = useState(1);
10+
const [isSidebarOpen, setIsSidebarOpen] = useState(false); // État pour ouvrir/fermer
1011
const loaderRef = useRef(null);
1112

12-
// Fonctions de modification (On reset la page ici, pas dans un effect)
1313
const handleSearchChange = (e) => {
1414
setSearch(e.target.value);
1515
setPage(1);
@@ -18,15 +18,14 @@ export default function App() {
1818
const handleCategoryChange = (cat) => {
1919
setActiveCategory(cat);
2020
setPage(1);
21+
setIsSidebarOpen(false); // Ferme la sidebar sur mobile après sélection
2122
};
2223

23-
// Extraction des catégories
2424
const categories = useMemo(() => {
2525
const cats = items.map(item => item.category);
2626
return ['all', ...new Set(cats)];
2727
}, []);
2828

29-
// Filtrage des items
3029
const allFilteredItems = useMemo(() => {
3130
return items.filter(item => {
3231
const matchesSearch = item.name.toLowerCase().includes(search.toLowerCase()) ||
@@ -36,17 +35,8 @@ export default function App() {
3635
});
3736
}, [search, activeCategory]);
3837

39-
// Calcul des items visibles en fonction de la page
4038
const visibleItems = allFilteredItems.slice(0, page * ITEMS_PER_PAGE);
4139

42-
// Intersection Observer (Seul effect autorisé pour l'API externe Browser)
43-
useEffect(() => {
44-
window.scrollTo(0, 0);
45-
if ('scrollRestoration' in window.history) {
46-
window.history.scrollRestoration = 'manual';
47-
}
48-
}, []);
49-
5040
// Scroll infini
5141
useEffect(() => {
5242
const observer = new IntersectionObserver((entries) => {
@@ -63,7 +53,7 @@ export default function App() {
6353
navigator.clipboard.writeText(id);
6454
const notification = document.createElement('div');
6555
notification.innerText = `Copié: ${id}`;
66-
notification.className = "fixed bottom-5 right-5 bg-[#c3a05b] text-black px-4 py-2 rounded-lg shadow-lg z-50 font-bold border border-white/20";
56+
notification.className = "fixed bottom-5 right-5 bg-[#c3a05b] text-black px-4 py-2 rounded-lg shadow-lg z-[100] font-bold border border-white/20";
6757
document.body.appendChild(notification);
6858
setTimeout(() => notification.remove(), 2000);
6959
};
@@ -74,12 +64,36 @@ export default function App() {
7464
::-webkit-scrollbar { width: 6px; }
7565
::-webkit-scrollbar-track { background: #0a0a0f; }
7666
::-webkit-scrollbar-thumb { background: #c3a05b; border-radius: 10px; }
77-
* { scrollbar-width: thin; scrollbar-color: #c3a05b #0a0a0f; }
67+
.sidebar-scroll { overscroll-behavior: contain; }
7868
`}} />
7969

80-
{/* Sidebar */}
81-
<aside className="w-64 border-r border-white/5 bg-[#0d0d14]/80 backdrop-blur-md sticky top-0 h-screen hidden md:flex flex-col">
82-
<div className="p-6 flex-1 overflow-y-auto">
70+
{/* --- MENU BURGER (Mobile) --- */}
71+
<button
72+
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
73+
className="fixed top-6 left-6 z-[70] md:hidden bg-[#c3a05b] p-3 rounded-xl text-black shadow-lg active:scale-90 transition-transform"
74+
>
75+
{isSidebarOpen ? (
76+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
77+
) : (
78+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
79+
)}
80+
</button>
81+
82+
{/* --- OVERLAY (Mobile) --- */}
83+
{isSidebarOpen && (
84+
<div
85+
className="fixed inset-0 bg-black/80 backdrop-blur-sm z-[60] md:hidden transition-opacity duration-300"
86+
onClick={() => setIsSidebarOpen(false)}
87+
/>
88+
)}
89+
90+
{/* --- SIDEBAR RESPONSIVE --- */}
91+
<aside className={`
92+
fixed md:sticky top-0 left-0 z-[65] w-72 md:w-64 h-screen border-r border-white/5 bg-[#0d0d14]/95 backdrop-blur-xl
93+
transition-transform duration-300 ease-out flex flex-col
94+
${isSidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
95+
`}>
96+
<div className="p-6 pt-20 md:pt-6 flex-1 overflow-y-auto sidebar-scroll">
8397
<h2 className="text-[#c3a05b] font-black uppercase tracking-widest text-[10px] mb-8 opacity-50">Navigation</h2>
8498
<nav className="space-y-1">
8599
{categories.map((cat) => (
@@ -106,42 +120,51 @@ export default function App() {
106120
</div>
107121
</aside>
108122

109-
{/* Main Content */}
110-
<main className="flex-1 p-6 lg:p-10">
111-
<header className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 mb-12">
123+
{/* --- MAIN CONTENT --- */}
124+
<main className="flex-1 p-4 md:p-10 transition-all duration-300">
125+
<header className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 mb-12 mt-16 md:mt-0">
112126
<div>
113-
<h1 className="text-4xl font-black tracking-tighter uppercase italic">
127+
<h1 className="text-3xl md:text-4xl font-black tracking-tighter uppercase italic">
114128
<span className="text-[#c3a05b]">Lunatic</span> Catalogue
115129
</h1>
116130
<p className="text-gray-500 text-[10px] font-mono uppercase tracking-[0.2em] mt-1">Items Lunatic RP</p>
117131
</div>
118132

119-
<input
120-
type="text"
121-
placeholder="Rechercher..."
122-
className="w-full lg:w-96 bg-[#11111a] border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-[#c3a05b]/50 transition-all shadow-2xl"
123-
onChange={handleSearchChange}
124-
/>
133+
<div className="relative group">
134+
<input
135+
type="text"
136+
placeholder="Rechercher un item..."
137+
className="w-full lg:w-96 bg-[#11111a] border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-[#c3a05b]/50 transition-all shadow-2xl"
138+
onChange={handleSearchChange}
139+
/>
140+
</div>
125141
</header>
126142

127-
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8 gap-4">
143+
{/* --- GRID ITEMS --- */}
144+
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8 gap-3 md:gap-4">
128145
{visibleItems.map((item) => (
129146
<div
130147
key={item.id}
131148
onClick={() => copyToClipboard(item.id)}
132-
className="group relative bg-[#11111a] border border-white/5 rounded-2xl p-4 flex flex-col items-center justify-between aspect-square hover:border-[#c3a05b]/40 hover:bg-[#c3a05b]/[0.02] transition-all duration-300 cursor-pointer"
149+
className="group relative bg-[#11111a] border border-white/5 rounded-2xl p-3 md:p-4 flex flex-col items-center justify-between aspect-square hover:border-[#c3a05b]/40 hover:bg-[#c3a05b]/[0.02] transition-all duration-300 cursor-pointer"
133150
>
134-
<div className="flex-1 flex items-center justify-center p-2">
135-
<img src={item.image.startsWith('./') ? item.image : `./${item.image}`} alt={item.name} className="max-w-[80%] max-h-[80%] object-contain group-hover:scale-110 transition-transform duration-500"/>
151+
<div className="flex-1 flex items-center justify-center p-2 w-full overflow-hidden">
152+
<img
153+
src={item.image.startsWith('./') ? item.image : `./${item.image}`}
154+
alt={item.name}
155+
loading="lazy"
156+
className="max-w-[85%] max-h-[85%] object-contain group-hover:scale-110 transition-transform duration-500"
157+
/>
136158
</div>
137159
<div className="w-full text-center mt-2">
138-
<p className="text-[10px] font-bold text-gray-400 group-hover:text-[#c3a05b] truncate uppercase">{item.name}</p>
139-
<p className="text-[8px] font-mono text-gray-700 mt-0.5">{item.category}</p>
160+
<p className="text-[9px] md:text-[10px] font-bold text-gray-400 group-hover:text-[#c3a05b] truncate uppercase">{item.name}</p>
161+
<p className="text-[7px] md:text-[8px] font-mono text-gray-700 mt-0.5">{item.category}</p>
140162
</div>
141163
</div>
142164
))}
143165
</div>
144166

167+
{/* --- LOADER --- */}
145168
<div ref={loaderRef} className="h-20 w-full flex items-center justify-center mt-10">
146169
{visibleItems.length < allFilteredItems.length && (
147170
<div className="w-6 h-6 border-2 border-[#c3a05b]/20 border-b-[#c3a05b] rounded-full animate-spin" />

0 commit comments

Comments
 (0)