@@ -1,16 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Suspense, useEffect } from "react";
|
import { Suspense, useEffect } from "react";
|
||||||
import { useParams } from "react-router";
|
|
||||||
import {
|
|
||||||
BookOpen,
|
|
||||||
Users,
|
|
||||||
ToyBrick,
|
|
||||||
LifeBuoy,
|
|
||||||
Send,
|
|
||||||
Settings,
|
|
||||||
Building,
|
|
||||||
FileText,
|
|
||||||
} from "lucide-react";
|
|
||||||
import {
|
import {
|
||||||
graphql,
|
graphql,
|
||||||
PreloadedQuery,
|
PreloadedQuery,
|
||||||
@@ -18,82 +7,12 @@ import {
|
|||||||
useQueryLoader,
|
useQueryLoader,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
|
|
||||||
import { NavMain } from "@/components/NavMain";
|
|
||||||
import { NavSecondary } from "@/components/NavSecondary";
|
|
||||||
import { NavUser } from "@/components/NavUser";
|
import { NavUser } from "@/components/NavUser";
|
||||||
import {
|
import { Sidebar } from "@/components/ui/sidebar";
|
||||||
Sidebar,
|
|
||||||
SidebarContent,
|
|
||||||
SidebarFooter,
|
|
||||||
SidebarHeader,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/components/ui/sidebar";
|
|
||||||
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
|
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
|
||||||
import { OrganizationSwitcher } from "@/components/OrganizationSwitcher";
|
import { OrganizationSwitcher } from "@/components/OrganizationSwitcher";
|
||||||
|
import { AppSidebarShell } from "./AppSidebarShell";
|
||||||
function getNavItems(organizationId?: string) {
|
import { AppSidebarSkeleton } from "./AppSidebarSkeleton";
|
||||||
// Always return the same structure, but with or without URLs depending on whether an organization is selected
|
|
||||||
return {
|
|
||||||
navMain: [
|
|
||||||
{
|
|
||||||
title: "Frameworks",
|
|
||||||
url: organizationId
|
|
||||||
? `/organizations/${organizationId}/frameworks`
|
|
||||||
: undefined,
|
|
||||||
icon: BookOpen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Organizations",
|
|
||||||
icon: Building,
|
|
||||||
url: organizationId
|
|
||||||
? `/organizations/${organizationId}/peoples`
|
|
||||||
: undefined,
|
|
||||||
items: organizationId
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
title: "Peoples",
|
|
||||||
url: `/organizations/${organizationId}/peoples`,
|
|
||||||
icon: Users,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Vendors",
|
|
||||||
url: `/organizations/${organizationId}/vendors`,
|
|
||||||
icon: ToyBrick,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Policies",
|
|
||||||
url: organizationId
|
|
||||||
? `/organizations/${organizationId}/policies`
|
|
||||||
: undefined,
|
|
||||||
icon: FileText,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: organizationId
|
|
||||||
? `/organizations/${organizationId}/settings`
|
|
||||||
: undefined,
|
|
||||||
icon: Settings,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
navSecondary: [
|
|
||||||
{
|
|
||||||
title: "Support",
|
|
||||||
url: "#",
|
|
||||||
icon: LifeBuoy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Feedback",
|
|
||||||
url: "#",
|
|
||||||
icon: Send,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppSidebarQuery = graphql`
|
const AppSidebarQuery = graphql`
|
||||||
query AppSidebarQuery {
|
query AppSidebarQuery {
|
||||||
@@ -111,68 +30,16 @@ function AppSidebarContent({
|
|||||||
}: React.ComponentProps<typeof Sidebar> & {
|
}: React.ComponentProps<typeof Sidebar> & {
|
||||||
queryRef: PreloadedQuery<AppSidebarQueryType>;
|
queryRef: PreloadedQuery<AppSidebarQueryType>;
|
||||||
}) {
|
}) {
|
||||||
const { organizationId } = useParams();
|
|
||||||
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
|
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
|
||||||
const navItems = getNavItems(organizationId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar variant="inset" {...props}>
|
<AppSidebarShell
|
||||||
<SidebarHeader>
|
organizationSwitcher={
|
||||||
<OrganizationSwitcher organizations={data.viewer} />
|
<OrganizationSwitcher organizations={data.viewer} />
|
||||||
</SidebarHeader>
|
}
|
||||||
<SidebarContent>
|
navUser={<NavUser viewer={data.viewer} />}
|
||||||
<NavMain items={navItems.navMain} />
|
{...props}
|
||||||
<NavSecondary items={navItems.navSecondary} className="mt-auto" />
|
/>
|
||||||
</SidebarContent>
|
|
||||||
<SidebarFooter>
|
|
||||||
<NavUser viewer={data.viewer} />
|
|
||||||
</SidebarFooter>
|
|
||||||
</Sidebar>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AppSidebarFallback(props: React.ComponentProps<typeof Sidebar>) {
|
|
||||||
return (
|
|
||||||
<Sidebar variant="inset" {...props}>
|
|
||||||
<SidebarHeader>
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<SidebarMenuButton size="lg">
|
|
||||||
<div className="h-8 w-8 rounded-lg bg-sidebar-muted" />
|
|
||||||
<div className="flex-1 space-y-1">
|
|
||||||
<div className="h-4 w-3/4 rounded-lg bg-sidebar-muted" />
|
|
||||||
<div className="h-3 w-1/2 rounded-lg bg-sidebar-muted" />
|
|
||||||
</div>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarHeader>
|
|
||||||
<SidebarContent>
|
|
||||||
<SidebarMenu>
|
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
|
||||||
<SidebarMenuItem key={i}>
|
|
||||||
<SidebarMenuButton>
|
|
||||||
<div className="h-4 w-4 rounded-lg bg-sidebar-muted" />
|
|
||||||
<div className="h-4 w-24 rounded-lg bg-sidebar-muted" />
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarContent>
|
|
||||||
<SidebarFooter>
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<SidebarMenuButton size="lg">
|
|
||||||
<div className="h-8 w-8 rounded-lg bg-sidebar-muted" />
|
|
||||||
<div className="flex-1 space-y-1">
|
|
||||||
<div className="h-4 w-3/4 rounded-lg bg-sidebar-muted" />
|
|
||||||
<div className="h-3 w-1/2 rounded-lg bg-sidebar-muted" />
|
|
||||||
</div>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarFooter>
|
|
||||||
</Sidebar>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,11 +52,11 @@ export function AppSidebar(props: React.ComponentProps<typeof Sidebar>) {
|
|||||||
}, [loadQuery]);
|
}, [loadQuery]);
|
||||||
|
|
||||||
if (!queryRef) {
|
if (!queryRef) {
|
||||||
return <AppSidebarFallback {...props} />;
|
return <AppSidebarSkeleton {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<AppSidebarFallback {...props} />}>
|
<Suspense fallback={<AppSidebarSkeleton {...props} />}>
|
||||||
<AppSidebarContent queryRef={queryRef} {...props} />
|
<AppSidebarContent queryRef={queryRef} {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
107
apps/console/src/components/AppSidebarShell.tsx
Normal file
107
apps/console/src/components/AppSidebarShell.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { useParams } from "react-router";
|
||||||
|
import { NavMain } from "./NavMain";
|
||||||
|
import { NavSecondary } from "./NavSecondary";
|
||||||
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarHeader,
|
||||||
|
} from "./ui/sidebar";
|
||||||
|
import {
|
||||||
|
BookOpen,
|
||||||
|
Users,
|
||||||
|
ToyBrick,
|
||||||
|
LifeBuoy,
|
||||||
|
Send,
|
||||||
|
Settings,
|
||||||
|
Building,
|
||||||
|
FileText,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
interface AppSidebarShellProps extends React.ComponentProps<typeof Sidebar> {
|
||||||
|
navUser: ReactNode;
|
||||||
|
organizationSwitcher: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppSidebarShell({
|
||||||
|
navUser,
|
||||||
|
organizationSwitcher,
|
||||||
|
...props
|
||||||
|
}: AppSidebarShellProps) {
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
const navItems = getNavItems(organizationId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sidebar variant="inset" {...props}>
|
||||||
|
<SidebarHeader>{organizationSwitcher}</SidebarHeader>
|
||||||
|
<SidebarContent>
|
||||||
|
<NavMain items={navItems.navMain} />
|
||||||
|
<NavSecondary items={navItems.navSecondary} className="mt-auto" />
|
||||||
|
</SidebarContent>
|
||||||
|
<SidebarFooter>{navUser}</SidebarFooter>
|
||||||
|
</Sidebar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNavItems(organizationId?: string) {
|
||||||
|
// Always return the same structure, but with or without URLs depending on whether an organization is selected
|
||||||
|
return {
|
||||||
|
navMain: [
|
||||||
|
{
|
||||||
|
title: "Frameworks",
|
||||||
|
url: organizationId
|
||||||
|
? `/organizations/${organizationId}/frameworks`
|
||||||
|
: undefined,
|
||||||
|
icon: BookOpen,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Organizations",
|
||||||
|
icon: Building,
|
||||||
|
url: organizationId
|
||||||
|
? `/organizations/${organizationId}/peoples`
|
||||||
|
: undefined,
|
||||||
|
items: organizationId
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: "Peoples",
|
||||||
|
url: `/organizations/${organizationId}/peoples`,
|
||||||
|
icon: Users,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Vendors",
|
||||||
|
url: `/organizations/${organizationId}/vendors`,
|
||||||
|
icon: ToyBrick,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Policies",
|
||||||
|
url: organizationId
|
||||||
|
? `/organizations/${organizationId}/policies`
|
||||||
|
: undefined,
|
||||||
|
icon: FileText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
url: organizationId
|
||||||
|
? `/organizations/${organizationId}/settings`
|
||||||
|
: undefined,
|
||||||
|
icon: Settings,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
navSecondary: [
|
||||||
|
{
|
||||||
|
title: "Support",
|
||||||
|
url: "#",
|
||||||
|
icon: LifeBuoy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Feedback",
|
||||||
|
url: "#",
|
||||||
|
icon: Send,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
16
apps/console/src/components/AppSidebarSkeleton.tsx
Normal file
16
apps/console/src/components/AppSidebarSkeleton.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { AppSidebarShell } from "./AppSidebarShell";
|
||||||
|
import { NavUserSkeleton } from "./NavUserSkeleton";
|
||||||
|
import { OrganizationSwitcherSkeleton } from "./OrganizationSwitcherSkeleton";
|
||||||
|
import { Sidebar } from "./ui/sidebar";
|
||||||
|
|
||||||
|
export function AppSidebarSkeleton(
|
||||||
|
props: React.ComponentProps<typeof Sidebar>
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<AppSidebarShell
|
||||||
|
organizationSwitcher={<OrganizationSwitcherSkeleton />}
|
||||||
|
navUser={<NavUserSkeleton />}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
120
apps/console/src/components/NavUserShell.tsx
Normal file
120
apps/console/src/components/NavUserShell.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
BadgeCheck,
|
||||||
|
Bell,
|
||||||
|
ChevronsUpDown,
|
||||||
|
CreditCard,
|
||||||
|
LogOut,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
|
|
||||||
|
export function NavUserSkeleton() {
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
|
const { logout } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await logout();
|
||||||
|
navigate("/login");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<SidebarMenuButton
|
||||||
|
isActive
|
||||||
|
size="lg"
|
||||||
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<Avatar className="h-9 w-9">
|
||||||
|
<AvatarFallback className="bg-slate-400 text-gray-100">
|
||||||
|
{currentUser.fullName.substring(0, 2).toUpperCase()}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-semibold">
|
||||||
|
{currentUser.fullName}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs text-sidebar-foreground/70">
|
||||||
|
{currentUser.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown className="ml-auto size-4" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||||
|
side={isMobile ? "bottom" : "right"}
|
||||||
|
align="end"
|
||||||
|
sideOffset={4}
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel className="p-0 font-normal">
|
||||||
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
|
<Avatar className="h-8 w-8">
|
||||||
|
<AvatarFallback>
|
||||||
|
{currentUser.fullName.substring(0, 2).toUpperCase()}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-semibold">
|
||||||
|
{currentUser.fullName}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs">{currentUser.email}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Sparkles />
|
||||||
|
Upgrade to Pro
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BadgeCheck />
|
||||||
|
Account
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<CreditCard />
|
||||||
|
Billing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Bell />
|
||||||
|
Notifications
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onClick={handleLogout}>
|
||||||
|
<LogOut />
|
||||||
|
Log out
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
apps/console/src/components/NavUserSkeleton.tsx
Normal file
38
apps/console/src/components/NavUserSkeleton.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ChevronsUpDown } from "lucide-react";
|
||||||
|
|
||||||
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
|
||||||
|
export function NavUserSkeleton() {
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton
|
||||||
|
isActive
|
||||||
|
disabled
|
||||||
|
size="lg"
|
||||||
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<Avatar className="h-9 w-9">
|
||||||
|
<AvatarFallback className="bg-slate-400 text-gray-100 animate-pulse" />
|
||||||
|
</Avatar>
|
||||||
|
<div className="grid flex-1">
|
||||||
|
<div className="py-[3px]">
|
||||||
|
<div className="h-3.5 animate-pulse w-20 rounded-sm bg-slate-500" />
|
||||||
|
</div>
|
||||||
|
<div className="py-[3px]">
|
||||||
|
<div className="h-3.5 animate-pulse w-30 rounded-sm bg-slate-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown className="ml-auto size-4" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ const LogoComponent = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return org.name.substring(0, 2).toUpperCase();
|
return org.name.substring(0, 2).toUpperCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,11 +145,7 @@ export function OrganizationSwitcher({
|
|||||||
{currentOrganization ? "Free" : "No organization selected"}
|
{currentOrganization ? "Free" : "No organization selected"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronsUpDown
|
<ChevronsUpDown className="ml-auto" />
|
||||||
className={`ml-auto ${
|
|
||||||
!currentOrganization ? "text-gray-400" : ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
|
|||||||
33
apps/console/src/components/OrganizationSwitcherSkeleton.tsx
Normal file
33
apps/console/src/components/OrganizationSwitcherSkeleton.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ChevronsUpDown } from "lucide-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
|
||||||
|
export function OrganizationSwitcherSkeleton() {
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton
|
||||||
|
size="lg"
|
||||||
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground gap-2.5"
|
||||||
|
>
|
||||||
|
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-slate-300 text-sidebar-primary-foreground animate-pulse" />
|
||||||
|
<div className="grid text-left leading-tight">
|
||||||
|
<div className="py-[1px]">
|
||||||
|
<div className="h-4.5 animate-pulse w-15 rounded-sm bg-slate-400" />
|
||||||
|
</div>
|
||||||
|
<div className="py-0.5">
|
||||||
|
<div className="h-3 animate-pulse w-8 rounded-sm bg-slate-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown className="ml-auto" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user