ui: improve sidebar

This commit is contained in:
Sosokker 2025-03-07 02:25:34 +07:00
parent 08f852a397
commit 4b772e20d0
3 changed files with 108 additions and 207 deletions

View File

@ -1,6 +1,7 @@
"use client";
import * as React from "react";
import { useEffect, useState } from "react";
import {
AudioWaveform,
BookOpen,
@ -12,146 +13,85 @@ import {
PieChart,
Settings2,
SquareTerminal,
User,
} from "lucide-react";
import { NavMain } from "./nav-main";
import { NavProjects } from "./nav-projects";
import { NavUser } from "./nav-user";
import { TeamSwitcher } from "./team-switcher";
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from "@/components/ui/sidebar";
import { useEffect } from "react";
import { NavCrops } from "./nav-crops";
import { fetchUserMe } from "@/api/user";
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/avatar.webp",
},
interface Team {
name: string;
logo: React.ComponentType;
plan: string;
}
import { LucideIcon } from "lucide-react";
interface NavItem {
title: string;
url: string;
icon: LucideIcon;
}
interface SidebarConfig {
teams: Team[];
navMain: NavItem[];
crops: NavItem[];
}
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
config?: SidebarConfig;
}
export function AppSidebar({ config, ...props }: AppSidebarProps) {
const defaultConfig: SidebarConfig = {
teams: [
{
name: "Farm 1",
logo: GalleryVerticalEnd,
plan: "Hatyai",
},
{
name: "Farm 2",
logo: AudioWaveform,
plan: "Songkla",
},
{
name: "Farm 3",
logo: Command,
plan: "Layong",
},
{ name: "Farm 1", logo: GalleryVerticalEnd, plan: "Hatyai" },
{ name: "Farm 2", logo: AudioWaveform, plan: "Songkla" },
{ name: "Farm 3", logo: Command, plan: "Layong" },
],
navMain: [
{
title: "Dashboard",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "Analytic",
url: "#",
},
{ title: "Farms", url: "/farms", icon: Map },
{ title: "Crops list", url: "/crops", icon: Frame },
{ title: "Inventory", url: "/inventory", icon: SquareTerminal },
{ title: "Marketplace Information", url: "/marketplace", icon: PieChart },
{ title: "Knowledge Hub", url: "/knowledge", icon: BookOpen },
{ title: "Users", url: "/users", icon: User },
{ title: "AI Chatbot", url: "/chatbot", icon: Bot },
{ title: "Settings", url: "/settings", icon: Settings2 },
],
},
{
title: "AI Chatbot",
url: "#",
icon: Bot,
items: [
{
title: "Main model",
url: "#",
},
],
},
{
title: "Documentation",
url: "#",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
projects: [
{
name: "Crops 1",
url: "#",
icon: Frame,
},
{
name: "Crops 2",
url: "#",
icon: PieChart,
},
{
name: "Crops 3",
url: "#",
icon: Map,
},
// Define crops with dynamic URLs replace [farmId] with your actual farm identifier as needed.
crops: [
{ title: "Crops 1", url: "/farms/[farmId]/crops/1", icon: Map },
{ title: "Crops 2", url: "/farms/[farmId]/crops/2", icon: Map },
{ title: "Crops 3", url: "/farms/[farmId]/crops/3", icon: Map },
],
};
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const [user, setUser] = React.useState<{ name: string; email: string; avatar: string }>({
// Allow external configuration override
const sidebarConfig = config || defaultConfig;
const [user, setUser] = useState<{ name: string; email: string; avatar: string }>({
name: "",
email: "",
avatar: "/avatars/avatar.webp",
});
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
async function getUser() {
try {
const data = await fetchUserMe();
let to_set = user;
to_set.name = data.user.UUID;
to_set.email = data.user.Email;
setUser(to_set);
setUser({
name: data.user.UUID,
email: data.user.Email,
avatar: data.user.Avatar || "/avatars/avatar.webp",
});
} catch (err: any) {
setError(err.message);
} finally {
@ -164,11 +104,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
<TeamSwitcher teams={sidebarConfig.teams} />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
<NavMain items={sidebarConfig.navMain} />
<div className="mt-6">
<NavCrops crops={sidebarConfig.crops} />
</div>
</SidebarContent>
<SidebarFooter>{loading ? "Loading..." : error ? error : <NavUser user={user} />}</SidebarFooter>
<SidebarRail />

View File

@ -0,0 +1,41 @@
"use client";
import { LucideIcon } from "lucide-react";
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
interface CropItem {
title: string;
url: string;
icon: LucideIcon;
}
interface NavCropsProps {
crops: CropItem[];
title?: string;
}
export function NavCrops({ crops, title = "Crops" }: NavCropsProps) {
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>{title}</SidebarGroupLabel>
<SidebarMenu>
{crops.map((crop) => (
<SidebarMenuItem key={crop.title}>
<SidebarMenuButton asChild>
<a href={crop.url}>
<crop.icon />
<span>{crop.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}

View File

@ -1,82 +0,0 @@
"use client";
import { Folder, Forward, MoreHorizontal, Trash2, type LucideIcon } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
export function NavProjects({
projects,
}: {
projects: {
name: string;
url: string;
icon: LucideIcon;
}[];
}) {
const { isMobile } = useSidebar();
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
{projects.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover>
<MoreHorizontal />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-48 rounded-lg"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}>
<DropdownMenuItem>
<Folder className="text-muted-foreground" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Forward className="text-muted-foreground" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 className="text-muted-foreground" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
<MoreHorizontal className="text-sidebar-foreground/70" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
);
}