Initial open-source release
This commit is contained in:
174
src/components/app-sidebar.tsx
Normal file
174
src/components/app-sidebar.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import { Home, Inbox, Settings } from "lucide-react";
|
||||
import { Link, useRouterState } from "@tanstack/react-router";
|
||||
import { useSidebar } from "@/components/ui/sidebar"; // import useSidebar hook
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarRail,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { ChatList } from "./ChatList";
|
||||
import { AppList } from "./AppList";
|
||||
|
||||
// Menu items.
|
||||
const items = [
|
||||
{
|
||||
title: "Apps",
|
||||
to: "/",
|
||||
icon: Home,
|
||||
},
|
||||
{
|
||||
title: "Chat",
|
||||
to: "/chat",
|
||||
icon: Inbox,
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: "/settings",
|
||||
icon: Settings,
|
||||
},
|
||||
];
|
||||
|
||||
// Hover state types
|
||||
type HoverState =
|
||||
| "start-hover:app"
|
||||
| "start-hover:chat"
|
||||
| "clear-hover"
|
||||
| "no-hover";
|
||||
|
||||
export function AppSidebar() {
|
||||
const { state, toggleSidebar } = useSidebar(); // retrieve current sidebar state
|
||||
const [hoverState, setHoverState] = useState<HoverState>("no-hover");
|
||||
const expandedByHover = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
(hoverState === "start-hover:app" || hoverState === "start-hover:chat") &&
|
||||
state === "collapsed"
|
||||
) {
|
||||
expandedByHover.current = true;
|
||||
toggleSidebar();
|
||||
}
|
||||
if (
|
||||
hoverState === "clear-hover" &&
|
||||
state === "expanded" &&
|
||||
expandedByHover.current
|
||||
) {
|
||||
toggleSidebar();
|
||||
expandedByHover.current = false;
|
||||
setHoverState("no-hover");
|
||||
}
|
||||
}, [hoverState, toggleSidebar, state, setHoverState]);
|
||||
|
||||
const routerState = useRouterState();
|
||||
const isAppRoute =
|
||||
routerState.location.pathname === "/" ||
|
||||
routerState.location.pathname.startsWith("/app-details");
|
||||
const isChatRoute = routerState.location.pathname === "/chat";
|
||||
|
||||
let selectedItem: string | null = null;
|
||||
if (hoverState === "start-hover:app") {
|
||||
selectedItem = "Apps";
|
||||
} else if (hoverState === "start-hover:chat") {
|
||||
selectedItem = "Chat";
|
||||
} else if (state === "expanded") {
|
||||
if (isAppRoute) {
|
||||
selectedItem = "Apps";
|
||||
} else if (isChatRoute) {
|
||||
selectedItem = "Chat";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
collapsible="icon"
|
||||
onMouseLeave={() => {
|
||||
setHoverState("clear-hover");
|
||||
}}
|
||||
>
|
||||
<SidebarContent className="overflow-hidden">
|
||||
<div className="flex mt-8">
|
||||
{/* Left Column: Menu items */}
|
||||
<div className="">
|
||||
<SidebarTrigger
|
||||
onMouseEnter={() => {
|
||||
setHoverState("clear-hover");
|
||||
}}
|
||||
/>
|
||||
<AppIcons onHoverChange={setHoverState} />
|
||||
</div>
|
||||
{/* Right Column: Chat List Section */}
|
||||
<div className="w-[240px]">
|
||||
<AppList show={selectedItem === "Apps"} />
|
||||
<ChatList show={selectedItem === "Chat"} />
|
||||
</div>
|
||||
</div>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
function AppIcons({
|
||||
onHoverChange,
|
||||
}: {
|
||||
onHoverChange: (state: HoverState) => void;
|
||||
}) {
|
||||
const routerState = useRouterState();
|
||||
const pathname = routerState.location.pathname;
|
||||
|
||||
return (
|
||||
// When collapsed: only show the main menu
|
||||
<SidebarGroup className="pr-0">
|
||||
{/* <SidebarGroupLabel>Dyad</SidebarGroupLabel> */}
|
||||
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => {
|
||||
const isActive =
|
||||
(item.to === "/" && pathname === "/") ||
|
||||
(item.to !== "/" && pathname.startsWith(item.to));
|
||||
|
||||
return (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
size="sm"
|
||||
className="font-medium w-14"
|
||||
>
|
||||
<Link
|
||||
to={item.to}
|
||||
className={`flex flex-col items-center gap-1 h-14 mb-2 rounded-2xl ${
|
||||
isActive ? "bg-sidebar-accent" : ""
|
||||
}`}
|
||||
onMouseEnter={() => {
|
||||
if (item.title === "Apps") {
|
||||
onHoverChange("start-hover:app");
|
||||
} else if (item.title === "Chat") {
|
||||
onHoverChange("start-hover:chat");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<item.icon className="h-5 w-5" />
|
||||
<span className={"text-xs"}>{item.title}</span>
|
||||
</div>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user