Sidebar: promote AI & MCP to nav, consolidate footer into account menu (#337)

This commit is contained in:
Ben Senescu 2026-07-03 13:23:18 -04:00 committed by GitHub
parent 615ecce033
commit 7ed574c408
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 85 additions and 64 deletions

View File

@ -9,8 +9,13 @@ import {
User, User,
X, X,
} from "lucide-react"; } from "lucide-react";
import { aiNavItem, getProjectNavGroups } from "@/client/navigation/items"; import {
connectNavGroup,
getProjectNavGroups,
} from "@/client/navigation/items";
import { ProjectSwitcher } from "@/client/features/projects/ProjectSwitcher"; import { ProjectSwitcher } from "@/client/features/projects/ProjectSwitcher";
import { ThemePreferenceMenuItems } from "@/client/components/ThemePreferenceMenuItems";
import { closeDropdown } from "@/client/lib/dropdown";
import { signOutAndRedirect, useSession } from "@/lib/auth-client"; import { signOutAndRedirect, useSession } from "@/lib/auth-client";
import { isHostedClientAuthMode } from "@/lib/auth-mode"; import { isHostedClientAuthMode } from "@/lib/auth-mode";
import { BILLING_ROUTE } from "@/shared/billing"; import { BILLING_ROUTE } from "@/shared/billing";
@ -63,7 +68,10 @@ function SidebarNavLink({
} }
export function Sidebar({ projectId, onNavigate, onClose }: SidebarProps) { export function Sidebar({ projectId, onNavigate, onClose }: SidebarProps) {
const navGroups = projectId ? getProjectNavGroups(projectId) : []; const navGroups = [
...(projectId ? getProjectNavGroups(projectId) : []),
connectNavGroup,
];
return ( return (
<div className="flex h-full w-60 flex-col bg-base-200"> <div className="flex h-full w-60 flex-col bg-base-200">
@ -125,39 +133,22 @@ function SidebarFooter({ onNavigate }: { onNavigate?: () => void }) {
const { data: session } = useSession(); const { data: session } = useSession();
const isHostedMode = isHostedClientAuthMode(); const isHostedMode = isHostedClientAuthMode();
const email = session?.user?.email; const email = session?.user?.email;
const { icon: aiIcon, label: aiLabel, ...aiLinkProps } = aiNavItem;
const closeMenu = () => {
closeDropdown();
onNavigate?.();
};
return ( return (
<div className="shrink-0 border-t border-base-300 px-2 py-2 pb-safe"> <div className="shrink-0 border-t border-base-300 px-2 py-2 pb-safe">
<SidebarNavLink
icon={aiIcon}
label={aiLabel}
onNavigate={onNavigate}
linkProps={aiLinkProps}
/>
<SidebarNavLink <SidebarNavLink
icon={CircleHelp} icon={CircleHelp}
label="Help & Community" label="Help & Community"
onNavigate={onNavigate} onNavigate={onNavigate}
linkProps={{ to: "/support" }} linkProps={{ to: "/support" }}
/> />
{isHostedMode ? (
<SidebarNavLink
icon={CreditCard}
label="Billing"
onNavigate={onNavigate}
linkProps={{ to: BILLING_ROUTE }}
/>
) : null}
<SidebarNavLink
icon={Settings}
label="Settings"
onNavigate={onNavigate}
linkProps={{ to: "/settings" }}
/>
{email ? ( {email ? (
isHostedMode ? (
<div className="dropdown dropdown-top w-full"> <div className="dropdown dropdown-top w-full">
<button <button
type="button" type="button"
@ -172,8 +163,29 @@ function SidebarFooter({ onNavigate }: { onNavigate?: () => void }) {
</button> </button>
<ul <ul
tabIndex={0} tabIndex={0}
className="dropdown-content z-30 menu mb-1 w-52 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg" className="dropdown-content z-30 menu mb-1 w-56 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg"
> >
<li>
<Link to="/settings" onClick={closeMenu}>
<Settings className="h-4 w-4" />
Settings
</Link>
</li>
{isHostedMode ? (
<li>
<Link to={BILLING_ROUTE} onClick={closeMenu}>
<CreditCard className="h-4 w-4" />
Billing
</Link>
</li>
) : null}
<ThemePreferenceMenuItems />
{isHostedMode ? (
<>
<li
aria-hidden
className="pointer-events-none my-1 h-px bg-base-300 p-0"
/>
<li> <li>
<button <button
type="button" type="button"
@ -184,17 +196,18 @@ function SidebarFooter({ onNavigate }: { onNavigate?: () => void }) {
Sign out Sign out
</button> </button>
</li> </li>
</>
) : null}
</ul> </ul>
</div> </div>
) : ( ) : (
<div className={navItemBaseClass}> <SidebarNavLink
<User className="h-4 w-4 shrink-0" /> icon={Settings}
<span className="truncate" data-ph-mask> label="Settings"
{email} onNavigate={onNavigate}
</span> linkProps={{ to: "/settings" }}
</div> />
) )}
) : null}
</div> </div>
); );
} }

View File

@ -3,14 +3,9 @@ import { useQuery } from "@tanstack/react-query";
import { Check, ChevronsUpDown, FolderCog } from "lucide-react"; import { Check, ChevronsUpDown, FolderCog } from "lucide-react";
import { getProjects } from "@/serverFunctions/projects"; import { getProjects } from "@/serverFunctions/projects";
import { setLastProjectId } from "@/client/lib/active-project"; import { setLastProjectId } from "@/client/lib/active-project";
import { closeDropdown } from "@/client/lib/dropdown";
import type { ProjectSummary } from "./types"; import type { ProjectSummary } from "./types";
function closeDropdown() {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}
export function ProjectSwitcher({ export function ProjectSwitcher({
activeProjectId, activeProjectId,
onCloseDrawer, onCloseDrawer,

View File

@ -0,0 +1,7 @@
// daisyUI dropdowns stay open until focus leaves, so blur the trigger to
// close the menu before navigating or acting on a selection.
export function closeDropdown() {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}

View File

@ -60,12 +60,18 @@ const projectNavItems = [
}, },
] as const; ] as const;
export const aiNavItem = linkOptions({ const aiNavItem = linkOptions({
to: "/ai" as const, to: "/ai" as const,
label: "AI & MCP", label: "AI & MCP",
icon: Bot, icon: Bot,
}); });
// Always-visible sidebar group (not project-scoped, unlike the groups below).
export const connectNavGroup = {
label: "Connect",
items: [aiNavItem],
};
function getProjectNavItems(projectId: string) { function getProjectNavItems(projectId: string) {
return linkOptions( return linkOptions(
projectNavItems.map((item) => ({ projectNavItems.map((item) => ({