@@ -13,6 +13,7 @@ import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/App
|
||||
import { OrganizationSwitcher } from "@/components/OrganizationSwitcher";
|
||||
import { AppSidebarShell } from "./AppSidebarShell";
|
||||
import { AppSidebarSkeleton } from "./AppSidebarSkeleton";
|
||||
import { NavMain } from "./NavMain";
|
||||
|
||||
const AppSidebarQuery = graphql`
|
||||
query AppSidebarQuery {
|
||||
@@ -37,6 +38,7 @@ function AppSidebarContent({
|
||||
organizationSwitcher={
|
||||
<OrganizationSwitcher organizations={data.viewer} />
|
||||
}
|
||||
navMain={<NavMain />}
|
||||
navUser={<NavUser viewer={data.viewer} />}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { useParams } from "react-router";
|
||||
import { NavMain } from "./NavMain";
|
||||
import { NavSecondary } from "./NavSecondary";
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -7,101 +5,28 @@ import {
|
||||
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;
|
||||
navMain: ReactNode;
|
||||
organizationSwitcher: ReactNode;
|
||||
}
|
||||
|
||||
export function AppSidebarShell({
|
||||
navUser,
|
||||
navMain,
|
||||
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" />
|
||||
{navMain}
|
||||
<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,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AppSidebarShell } from "./AppSidebarShell";
|
||||
import { NavMainSkeleton } from "./NavMainSkeleton";
|
||||
import { NavUserSkeleton } from "./NavUserSkeleton";
|
||||
import { OrganizationSwitcherSkeleton } from "./OrganizationSwitcherSkeleton";
|
||||
import { Sidebar } from "./ui/sidebar";
|
||||
@@ -9,6 +10,7 @@ export function AppSidebarSkeleton(
|
||||
return (
|
||||
<AppSidebarShell
|
||||
organizationSwitcher={<OrganizationSwitcherSkeleton />}
|
||||
navMain={<NavMainSkeleton />}
|
||||
navUser={<NavUserSkeleton />}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -19,71 +19,35 @@ import {
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import {
|
||||
BookOpen,
|
||||
Building,
|
||||
FileText,
|
||||
Settings,
|
||||
ToyBrick,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import { NavMainSkeleton } from "./NavMainSkeleton";
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
interface NavItem {
|
||||
title: string;
|
||||
url?: string;
|
||||
icon: LucideIcon;
|
||||
isActive?: boolean;
|
||||
items?: {
|
||||
title: string;
|
||||
url?: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
isActive?: boolean;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
}) {
|
||||
}
|
||||
|
||||
export function NavMain() {
|
||||
const location = useLocation();
|
||||
const { organizationId } = useParams();
|
||||
const items: NavItem[] = getNavItems(organizationId);
|
||||
|
||||
const noOrganizationSelected = organizationId === undefined;
|
||||
|
||||
if (noOrganizationSelected) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>
|
||||
<div className="h-4 w-28 rounded-md bg-gray-200 animate-pulse" />
|
||||
</SidebarGroupLabel>
|
||||
<SidebarMenu className="space-y-1.5">
|
||||
{/* First item - simple item */}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||
<div className="h-4 w-32 rounded-md bg-gray-200 ml-2" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Second item - with dropdown */}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||
<div className="h-4 w-28 rounded-md bg-gray-200 ml-2" />
|
||||
<div className="ml-auto">
|
||||
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
<SidebarMenuSub>
|
||||
{[1, 2].map((i) => (
|
||||
<SidebarMenuSubItem key={i}>
|
||||
<SidebarMenuSubButton className="animate-pulse pl-8">
|
||||
<div className="h-3 w-20 rounded-md bg-gray-200" />
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Third item - simple item */}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||
<div className="h-4 w-24 rounded-md bg-gray-200 ml-2" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
if (!organizationId) {
|
||||
return <NavMainSkeleton />;
|
||||
}
|
||||
|
||||
const isItemActive = (item: { url?: string; items?: { url: string }[] }) => {
|
||||
@@ -171,3 +135,51 @@ export function NavMain({
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function getNavItems(organizationId?: string): NavItem[] {
|
||||
// Always return the same structure, but with or without URLs depending on whether an organization is selected
|
||||
return [
|
||||
{
|
||||
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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
55
apps/console/src/components/NavMainSkeleton.tsx
Normal file
55
apps/console/src/components/NavMainSkeleton.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function NavMainSkeleton() {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel className="pl-3">
|
||||
<div className="py-0.5">
|
||||
<div className="h-3 w-24 rounded-sm bg-gray-300 animate-pulse" />
|
||||
</div>
|
||||
</SidebarGroupLabel>
|
||||
<SidebarMenu className="space-y-1.5">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-5" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-gray-300" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-6" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-gray-300" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-6" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-gray-300" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-6" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-gray-300" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { type LucideIcon } from "lucide-react";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
import { LifeBuoy, Send } from "lucide-react";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
@@ -10,43 +9,27 @@ import {
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
}[];
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
const location = useLocation();
|
||||
const noOrganizationSelected = location.pathname === "/";
|
||||
|
||||
if (noOrganizationSelected) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{[1, 2].map((i) => (
|
||||
<SidebarMenuItem key={i}>
|
||||
<SidebarMenuButton size="sm" className="animate-pulse">
|
||||
<div className="h-3 w-3 rounded-lg bg-gray-200" />
|
||||
<div className="h-3 w-16 rounded-lg bg-gray-200 ml-2" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
const navItems = [
|
||||
{
|
||||
title: "Support",
|
||||
url: "#",
|
||||
icon: LifeBuoy,
|
||||
},
|
||||
{
|
||||
title: "Feedback",
|
||||
url: "#",
|
||||
icon: Send,
|
||||
},
|
||||
];
|
||||
|
||||
export function NavSecondary(
|
||||
props: React.ComponentPropsWithoutRef<typeof SidebarGroup>
|
||||
) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
{navItems.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton
|
||||
asChild={item.url !== "#"}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { NavUser_viewer$key } from "./__generated__/NavUser_viewer.graphql";
|
||||
import { NavUserSkeleton } from "./NavUserSkeleton";
|
||||
|
||||
export const navUserFragment = graphql`
|
||||
fragment NavUser_viewer on User {
|
||||
@@ -44,28 +45,14 @@ export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
||||
const { logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { organizationId } = useParams();
|
||||
const noOrganizationSelected = !organizationId;
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
if (noOrganizationSelected) {
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" className="animate-pulse">
|
||||
<div className="h-8 w-8 rounded-lg bg-gray-200" />
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="h-4 w-3/4 rounded-lg bg-gray-200" />
|
||||
<div className="h-3 w-1/2 rounded-lg bg-gray-200" />
|
||||
</div>
|
||||
<div className="ml-auto h-4 w-4 rounded-lg bg-gray-200" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
if (!organizationId) {
|
||||
return <NavUserSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronsUpDown } from "lucide-react";
|
||||
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
@@ -17,20 +14,14 @@ export function NavUserSkeleton() {
|
||||
isActive
|
||||
disabled
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground animate-pulse"
|
||||
>
|
||||
<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 className="bg-gray-400 size-9 rounded-full animate-pulse" />
|
||||
<div className="flex-1 space-y-[3px] animate-pulse">
|
||||
<div className="h-3.5 w-20 rounded-sm bg-gray-400" />
|
||||
<div className="h-3.5 w-30 rounded-sm bg-gray-400" />
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
<div className="ml-auto size-4 rounded-lg bg-gray-400" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { ChevronsUpDown, Plus, Building } from "lucide-react";
|
||||
import { ChevronsUpDown, Plus } from "lucide-react";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
import { Link, useNavigate, useParams } from "react-router";
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
OrganizationSwitcher_organizations$data,
|
||||
} from "./__generated__/OrganizationSwitcher_organizations.graphql";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { OrganizationSwitcherSkeleton } from "./OrganizationSwitcherSkeleton";
|
||||
|
||||
export const organizationSwitcherFragment = graphql`
|
||||
fragment OrganizationSwitcher_organizations on User {
|
||||
@@ -95,20 +96,7 @@ export function OrganizationSwitcher({
|
||||
};
|
||||
|
||||
if (!hasOrganizations) {
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" className="animate-pulse">
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-gray-200" />
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="h-4 w-3/4 rounded-lg bg-gray-200" />
|
||||
<div className="h-3 w-1/2 rounded-lg bg-gray-200" />
|
||||
</div>
|
||||
<div className="ml-auto h-4 w-4 rounded-lg bg-gray-200" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
return <OrganizationSwitcherSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -118,32 +106,33 @@ export function OrganizationSwitcher({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className={`data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground gap-2.5 ${
|
||||
!currentOrganization
|
||||
? "border border-dashed border-gray-400"
|
||||
: ""
|
||||
}`}
|
||||
className={cn(
|
||||
"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground gap-2.5",
|
||||
!currentOrganization && "border border-dashed border-gray-300"
|
||||
)}
|
||||
>
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-slate-400 text-sidebar-primary-foreground overflow-hidden">
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-slate-400 text-gray-100">
|
||||
{currentOrganization ? (
|
||||
<LogoComponent org={currentOrganization} className="size-8" />
|
||||
) : (
|
||||
<Building className="size-8 text-gray-400" />
|
||||
<div className="size-8 bg-slate-400 rounded-sm" />
|
||||
)}
|
||||
</div>
|
||||
<div className="grid text-left leading-tight">
|
||||
<span
|
||||
className={`truncate font-medium text-lg leading-5 ${
|
||||
!currentOrganization ? "text-gray-500" : "text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{currentOrganization
|
||||
? currentOrganization.name
|
||||
: "Select Organization"}
|
||||
</span>
|
||||
<span className="truncate text-xs text-gray-500 font-medium">
|
||||
{currentOrganization ? "Free" : "No organization selected"}
|
||||
</span>
|
||||
{currentOrganization ? (
|
||||
<>
|
||||
<span className="truncate font-medium text-lg leading-5 text-gray-900">
|
||||
{currentOrganization.name}
|
||||
</span>
|
||||
<span className="truncate text-xs text-gray-500 font-medium">
|
||||
Free
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="truncate text-gray-500 font-medium">
|
||||
Select Organization
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto" />
|
||||
</SidebarMenuButton>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronsUpDown } from "lucide-react";
|
||||
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
@@ -16,16 +14,16 @@ export function OrganizationSwitcherSkeleton() {
|
||||
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="size-8 items-center justify-center rounded-md bg-gray-300 animate-pulse" />
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="py-[1px]">
|
||||
<div className="h-4.5 animate-pulse w-15 rounded-sm bg-slate-400" />
|
||||
<div className="h-4.5 animate-pulse w-16 rounded-sm bg-gray-300" />
|
||||
</div>
|
||||
<div className="py-0.5">
|
||||
<div className="h-3 animate-pulse w-8 rounded-sm bg-slate-400" />
|
||||
<div className="h-3 animate-pulse w-8 rounded-sm bg-gray-300" />
|
||||
</div>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto" />
|
||||
<div className="ml-auto size-4 rounded-lg bg-gray-300 animate-pulse" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
|
||||
Reference in New Issue
Block a user