import gradio as gr class SidebarMenu(gr.HTML): def __init__( self, menu_data, value=None, open=True, position="left", width=280, **kwargs ): html_template = """ """ 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" }