EVio / sidebar.py
eva1267890's picture
Create sidebar.py
30517cc verified
Raw
History Blame Contribute Delete
10.2 kB
import gradio as gr
class SidebarMenu(gr.HTML):
def __init__(
self,
menu_data,
value=None,
open=True,
position="left",
width=280,
**kwargs
):
html_template = """
<div class="sidebar ${position} ${open ? 'open' : ''}"
style="width:${width}px; ${position}:-${width}px;">
<button class="toggle-button">
<i data-lucide="chevron-right" class="toggle-icon"></i>
</button>
<div class="sidebar-content">
<div class="brand">
<div class="logo-circle">
🌱
</div>
<span>EVio</span>
</div>
<div class="sidebar-wrapper">
${menu_data.map(item => {
if(item.type === "group") {
return `
<div class="menu-group">
<div class="group-header">
<div class="header-left">
<i
data-lucide="${item.icon}"
class="icon">
</i>
<span class="label">
${item.label}
</span>
</div>
<i
data-lucide="chevron-down"
class="arrow">
</i>
</div>
<div class="group-children">
${item.children.map(child => `
<div
class="menu-item ${value === child.id ? 'active':''}"
data-id="${child.id}">
<i
data-lucide="${child.icon}"
class="icon">
</i>
<span>
${child.label}
</span>
</div>
`).join("")}
</div>
</div>
`
}
return `
<div
class="menu-item ${value === item.id ? 'active':''}"
data-id="${item.id}">
<i
data-lucide="${item.icon}"
class="icon">
</i>
<span>
${item.label}
</span>
</div>
`
}).join("")}
</div>
</div>
</div>
"""
css_template = """
:root{
--evio-green:#4CAF50;
--evio-light:#EAF7EC;
--evio-soft:#D8F0DA;
--evio-dark:#245B2A;
--evio-border:#B8DDBB;
}
.sidebar{
display:flex;
flex-direction:column;
position:fixed;
top:0;
height:100%;
background:
linear-gradient(
180deg,
#F3FFF4,
#E8F5E9
);
border-right:
1px solid var(--evio-border);
color:var(--evio-dark);
font-family:
Inter,
sans-serif;
z-index:1000;
transition:
transform .35s ease;
box-shadow:
8px 0 25px rgba(76,175,80,.12);
}
.sidebar.open.left{
transform:
translateX(100%);
}
.sidebar.open.right{
transform:
translateX(-100%);
}
/* EVio Logo */
.brand{
display:flex;
align-items:center;
gap:12px;
padding:
25px 20px;
font-size:24px;
font-weight:800;
color:
var(--evio-dark);
}
.logo-circle{
width:42px;
height:42px;
display:flex;
align-items:center;
justify-content:center;
background:
white;
border-radius:14px;
box-shadow:
0 5px 15px rgba(76,175,80,.2);
}
.toggle-button{
position:absolute;
top:25px;
right:-40px;
width:35px;
height:35px;
border-radius:
0 12px 12px 0;
border:
1px solid var(--evio-border);
background:white;
cursor:pointer;
display:flex;
align-items:center;
justify-content:center;
}
.toggle-button:hover{
background:
var(--evio-light);
}
.toggle-icon{
color:
var(--evio-green);
width:20px;
height:20px;
}
.sidebar-content{
overflow-y:auto;
}
.sidebar-wrapper{
padding:
10px 15px;
}
.menu-item{
display:flex;
align-items:center;
gap:12px;
padding:
14px 16px;
margin:
7px 0;
border-radius:
14px;
cursor:pointer;
font-size:15px;
font-weight:600;
color:
var(--evio-dark);
transition:.25s;
}
.menu-item:hover{
background:
linear-gradient(
90deg,
#A5D6A7,
#66BB6A
);
color:white;
transform:
translateX(6px);
box-shadow:
0 8px 20px rgba(76,175,80,.25);
}
.menu-item.active{
background:
linear-gradient(
90deg,
#66BB6A,
#4CAF50
);
color:white;
box-shadow:
0 8px 22px rgba(76,175,80,.35);
}
.group-header{
display:flex;
justify-content:space-between;
align-items:center;
padding:
13px 16px;
cursor:pointer;
border-radius:14px;
font-weight:700;
color:
var(--evio-dark);
}
.group-header:hover{
background:
var(--evio-soft);
}
.header-left{
display:flex;
align-items:center;
gap:12px;
}
.group-children{
display:none;
padding-left:
15px;
}
.icon{
width:19px;
height:19px;
stroke-width:2.3;
color:
var(--evio-green);
transition:.25s;
}
.menu-item:hover .icon{
color:white;
transform:scale(1.15);
}
.arrow{
width:15px;
height:15px;
color:
var(--evio-green);
transition:.3s;
}
@media(max-width:768px){
.sidebar{
width:100vw!important;
}
}
"""
js_on_load = """
let openFolders = new Set();
function initIcons(){
if(window.lucide){
window.lucide.createIcons();
}
}
function folders(){
element.querySelectorAll(".group-header")
.forEach(header=>{
let label =
header.querySelector(".label")
?.innerText;
let children =
header.nextElementSibling;
let arrow =
header.querySelector(".arrow");
if(children){
let open =
openFolders.has(label);
children.style.display =
open ? "block":"none";
arrow.style.transform =
open ?
"rotate(180deg)"
:
"rotate(0deg)";
}
});
}
initIcons();
folders();
element.addEventListener(
"click",
e=>{
if(e.target.closest(".toggle-button")){
let sidebar =
element.querySelector(".sidebar");
sidebar.classList.toggle("open");
return;
}
let header =
e.target.closest(".group-header");
if(header){
let label =
header.querySelector(".label")
.innerText;
openFolders.has(label)
?
openFolders.delete(label)
:
openFolders.add(label);
folders();
return;
}
let item =
e.target.closest(".menu-item");
if(item){
props.value =
item.dataset.id;
trigger("change");
}
});
"""
super().__init__(
value=value,
html=html_template,
**kwargs
)
self.css = css_template
self.js = js_on_load
def api_info(self):
return {
"type":"string"
}