Sidebar: promote AI & MCP to nav, consolidate footer into account menu (#337)
This commit is contained in:
parent
615ecce033
commit
7ed574c408
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
7
src/client/lib/dropdown.ts
Normal file
7
src/client/lib/dropdown.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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) => ({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user