164 lines
6.3 KiB
TypeScript
164 lines
6.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { useStore } from "../store";
|
||
|
||
function useWindowWidth() {
|
||
const [width, setWidth] = useState(window.innerWidth);
|
||
useEffect(() => {
|
||
const handler = () => setWidth(window.innerWidth);
|
||
window.addEventListener("resize", handler);
|
||
return () => window.removeEventListener("resize", handler);
|
||
}, []);
|
||
return width;
|
||
}
|
||
|
||
export default function Sidebar() {
|
||
const { guides, openGuide, activeGuideGid, view, sidebarCollapsed, setSidebarCollapsed } = useStore();
|
||
const [search, setSearch] = useState("");
|
||
const collapsed = sidebarCollapsed;
|
||
const windowWidth = useWindowWidth();
|
||
const isOverlay = collapsed || windowWidth < 500;
|
||
|
||
const filtered = guides.filter(g =>
|
||
g.name.toLowerCase().includes(search.toLowerCase())
|
||
);
|
||
|
||
return (
|
||
<aside style={{
|
||
position: isOverlay ? "absolute" : "relative",
|
||
left: 0,
|
||
top: 0,
|
||
bottom: 0,
|
||
zIndex: 10,
|
||
width: collapsed ? "36px" : "190px",
|
||
flexShrink: 0,
|
||
background: collapsed ? "transparent" : "#161b22",
|
||
borderRight: collapsed ? "none" : "1px solid #2d3748",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
overflow: "hidden",
|
||
transition: "width 0.2s ease, background 0.2s ease",
|
||
}}>
|
||
{/* Toggle button */}
|
||
<button
|
||
onClick={() => setSidebarCollapsed(!sidebarCollapsed)} title={collapsed ? "Ouvrir le menu" : "Réduire le menu"}
|
||
style={{
|
||
width: "100%",
|
||
height: "36px",
|
||
flexShrink: 0,
|
||
background: collapsed ? "rgba(22,27,34,0.9)" : "transparent",
|
||
border: collapsed ? "1px solid #2d3748" : "none",
|
||
borderLeft: "none",
|
||
borderBottom: collapsed ? "1px solid #2d3748" : "1px solid #2d3748",
|
||
borderRadius: collapsed ? "0 6px 6px 0" : "0",
|
||
color: "#4a5568",
|
||
cursor: "pointer",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: collapsed ? "center" : "flex-end",
|
||
padding: "0 10px",
|
||
marginTop: collapsed ? "8px" : "0",
|
||
transition: "all 0.15s",
|
||
}}
|
||
onMouseEnter={e => (e.currentTarget.style.color = "#f0c040")}
|
||
onMouseLeave={e => (e.currentTarget.style.color = "#4a5568")}
|
||
>
|
||
<span style={{
|
||
fontSize: "12px",
|
||
transform: collapsed ? "rotate(180deg)" : "rotate(0deg)",
|
||
transition: "transform 0.2s ease",
|
||
display: "inline-block",
|
||
}}>
|
||
‹
|
||
</span>
|
||
</button>
|
||
|
||
{!collapsed && (
|
||
<>
|
||
{/* Search */}
|
||
<div style={{ padding: "10px 12px", borderBottom: "1px solid #2d3748" }}>
|
||
<input
|
||
value={search}
|
||
onChange={e => setSearch(e.target.value)}
|
||
placeholder="Rechercher un Dofus…"
|
||
style={{
|
||
width: "100%", background: "#0d1117", border: "1px solid #2d3748",
|
||
borderRadius: "6px", padding: "6px 10px", color: "#e2e8f0",
|
||
fontSize: "12px", outline: "none", boxSizing: "border-box",
|
||
}}
|
||
onFocus={e => (e.target.style.borderColor = "#f0c040")}
|
||
onBlur={e => (e.target.style.borderColor = "#2d3748")}
|
||
/>
|
||
</div>
|
||
|
||
{/* Guide list */}
|
||
<div style={{ flex: 1, overflowY: "auto", padding: "8px 0", scrollbarWidth: "none" }}>
|
||
{filtered.length === 0 ? (
|
||
<div style={{ padding: "16px 12px", color: "#4a5568", fontSize: "12px", textAlign: "center" }}>
|
||
Aucun guide synchronisé
|
||
</div>
|
||
) : (
|
||
filtered.map(guide => {
|
||
const pct = guide.total_quests > 0
|
||
? Math.round((guide.completed_quests / guide.total_quests) * 100)
|
||
: 0;
|
||
const isActive = guide.gid === activeGuideGid && view === "guide";
|
||
|
||
return (
|
||
<button
|
||
key={guide.gid}
|
||
onClick={() => openGuide(guide.gid)}
|
||
style={{
|
||
width: "100%", textAlign: "left",
|
||
background: isActive ? "rgba(240,192,64,0.08)" : "transparent",
|
||
border: "none", borderLeft: isActive ? "2px solid #f0c040" : "2px solid transparent",
|
||
padding: "8px 12px", cursor: "pointer", transition: "all 0.12s",
|
||
}}
|
||
onMouseEnter={e => {
|
||
if (!isActive) (e.currentTarget as HTMLElement).style.background = "rgba(255,255,255,0.03)";
|
||
}}
|
||
onMouseLeave={e => {
|
||
if (!isActive) (e.currentTarget as HTMLElement).style.background = "transparent";
|
||
}}
|
||
>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "4px" }}>
|
||
<span style={{
|
||
fontSize: "12px", fontWeight: isActive ? 600 : 400,
|
||
color: isActive ? "#f0c040" : "#e2e8f0",
|
||
display: "-webkit-box",
|
||
WebkitLineClamp: 2,
|
||
WebkitBoxOrient: "vertical",
|
||
overflow: "hidden",
|
||
wordBreak: "break-word",
|
||
lineHeight: 1.3,
|
||
flex: 1,
|
||
} as React.CSSProperties}>
|
||
{guide.name}
|
||
</span>
|
||
<span style={{
|
||
fontSize: "10px", color: pct === 100 ? "#4ade80" : "#94a3b8",
|
||
fontWeight: 600, flexShrink: 0,
|
||
}}>
|
||
{pct}%
|
||
</span>
|
||
</div>
|
||
<div style={{
|
||
marginTop: "4px", height: "2px", background: "#2d3748",
|
||
borderRadius: "1px", overflow: "hidden",
|
||
}}>
|
||
<div style={{
|
||
height: "100%", width: `${pct}%`,
|
||
background: pct === 100 ? "#4ade80" : pct > 50 ? "#f0c040" : "#4a9eff",
|
||
transition: "width 0.3s ease",
|
||
}} />
|
||
</div>
|
||
</button>
|
||
);
|
||
})
|
||
)}
|
||
</div>
|
||
</>
|
||
)}
|
||
</aside>
|
||
);
|
||
}
|