mirror of
https://github.com/Shadowfita/docmost.git
synced 2025-11-14 16:51:07 +10:00
add settings navigation
This commit is contained in:
@ -10,15 +10,20 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.3.0",
|
"@hookform/resolvers": "^3.3.1",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-popover": "^1.0.6",
|
||||||
"@radix-ui/react-scroll-area": "^1.0.4",
|
"@radix-ui/react-scroll-area": "^1.0.4",
|
||||||
|
"@radix-ui/react-select": "^1.2.2",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-toast": "^1.1.4",
|
"@radix-ui/react-toast": "^1.1.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.6",
|
"@radix-ui/react-tooltip": "^1.0.6",
|
||||||
"@tabler/icons-react": "^2.32.0",
|
"@tabler/icons-react": "^2.32.0",
|
||||||
"@tanstack/react-query": "^4.33.0",
|
"@tanstack/react-query": "^4.33.0",
|
||||||
|
"@tanstack/react-table": "^8.9.3",
|
||||||
"@types/node": "20.4.8",
|
"@types/node": "20.4.8",
|
||||||
"@types/react": "18.2.18",
|
"@types/react": "18.2.18",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
@ -26,9 +31,11 @@
|
|||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"cmdk": "^0.2.0",
|
||||||
"eslint": "8.46.0",
|
"eslint": "8.46.0",
|
||||||
"eslint-config-next": "13.4.13",
|
"eslint-config-next": "13.4.13",
|
||||||
"jotai": "^2.3.1",
|
"jotai": "^2.3.1",
|
||||||
|
"jotai-optics": "^0.3.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"next": "13.4.13",
|
"next": "13.4.13",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
@ -36,12 +43,15 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.45.4",
|
"react-hook-form": "^7.45.4",
|
||||||
|
"react-hot-toast": "^2.4.1",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"tailwindcss": "3.3.3",
|
"tailwindcss": "3.3.3",
|
||||||
"tailwindcss-animate": "^1.0.6",
|
"tailwindcss-animate": "^1.0.6",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6",
|
||||||
|
"zod": "^3.22.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-cookie": "^3.0.3"
|
"@types/js-cookie": "^3.0.3",
|
||||||
|
"optics-ts": "^2.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import {
|
|
||||||
IconHome,
|
|
||||||
IconSearch,
|
|
||||||
IconSettings,
|
|
||||||
IconFilePlus,
|
|
||||||
} from '@tabler/icons-react';
|
|
||||||
|
|
||||||
export type NavigationMenuType = {
|
|
||||||
label: string;
|
|
||||||
path: string;
|
|
||||||
icon: ReactNode;
|
|
||||||
isActive?: boolean;
|
|
||||||
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLButtonElement>;
|
|
||||||
};
|
|
||||||
export const navigationMenu: NavigationMenuType[] = [
|
|
||||||
{
|
|
||||||
label: 'Home',
|
|
||||||
path: '',
|
|
||||||
icon: <IconHome size={16} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Search',
|
|
||||||
path: '',
|
|
||||||
icon: <IconSearch size={16} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Settings',
|
|
||||||
path: '',
|
|
||||||
icon: <IconSettings size={16} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'New Page',
|
|
||||||
path: '',
|
|
||||||
icon: <IconFilePlus size={16} />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import {
|
||||||
|
IconHome,
|
||||||
|
IconSearch,
|
||||||
|
IconSettings,
|
||||||
|
IconFilePlus,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
import NavigationLink from "@/components/sidebar/navigation/navigation-link";
|
||||||
|
import ButtonWithIcon from "@/components/ui/button-with-icon";
|
||||||
|
|
||||||
|
export type NavigationMenuType = {
|
||||||
|
label: string;
|
||||||
|
path: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
target?: string,
|
||||||
|
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLButtonElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const navigationMenu: NavigationMenuType[] = [
|
||||||
|
{
|
||||||
|
label: 'Home',
|
||||||
|
path: '',
|
||||||
|
icon: <IconHome size={16} />,
|
||||||
|
target: '/home',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search',
|
||||||
|
path: '',
|
||||||
|
icon: <IconSearch size={16} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
path: '',
|
||||||
|
icon: <IconSettings size={16} />,
|
||||||
|
target: '/settings/account'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'New Page',
|
||||||
|
path: '',
|
||||||
|
icon: <IconFilePlus size={16} />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const renderMenuItem = (menu, index) => {
|
||||||
|
if (menu.target) {
|
||||||
|
return (
|
||||||
|
<NavigationLink
|
||||||
|
href={menu.target}
|
||||||
|
icon={menu.icon}
|
||||||
|
className="w-full flex flex-1 justify-start items-center"
|
||||||
|
>
|
||||||
|
{menu.label}
|
||||||
|
</NavigationLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonWithIcon
|
||||||
|
key={index}
|
||||||
|
icon={menu.icon}
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full flex flex-1 justify-start items-center"
|
||||||
|
// onClick={}
|
||||||
|
>
|
||||||
|
<span className="text-ellipsis overflow-hidden">{menu.label}</span>
|
||||||
|
</ButtonWithIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface NavigationLinkProps {
|
||||||
|
children: ReactNode,
|
||||||
|
href: string;
|
||||||
|
icon?: ReactNode;
|
||||||
|
variant?: "default" | "ghost" | "outline";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NavigationLink({ children, href, icon, variant = "ghost", className }: NavigationLinkProps) {
|
||||||
|
return (
|
||||||
|
<Link href={href} className={cn(buttonVariants({ variant: variant }), className)}>
|
||||||
|
{icon && <span className="mr-[8px]">
|
||||||
|
{icon}
|
||||||
|
</span>}
|
||||||
|
<span className="text-ellipsis overflow-hidden">
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
40
frontend/src/components/sidebar/navigation/navigation.tsx
Normal file
40
frontend/src/components/sidebar/navigation/navigation.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { SidebarSection } from "@/components/sidebar/sidebar-section";
|
||||||
|
import { navigationMenu, renderMenuItem } from "@/components/sidebar/navigation/navigation-items";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import NavigationLink from "@/components/sidebar/navigation/navigation-link";
|
||||||
|
import { IconFileText } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Navigation() {
|
||||||
|
return (
|
||||||
|
<div className="pt-8">
|
||||||
|
<PrimaryNavigation />
|
||||||
|
<SecondaryNavigationArea />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrimaryNavigation() {
|
||||||
|
return (
|
||||||
|
<SidebarSection className="pb-2 mb-4 select-none border-b">
|
||||||
|
{navigationMenu.map(renderMenuItem)}
|
||||||
|
</SidebarSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SecondaryNavigationArea() {
|
||||||
|
return (
|
||||||
|
<ScrollArea className="h-[70vh]">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<NavigationLink
|
||||||
|
href="#"
|
||||||
|
className="w-full justify-start"
|
||||||
|
icon={<IconFileText size={16} />}
|
||||||
|
>
|
||||||
|
Welcome page
|
||||||
|
</NavigationLink>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -32,10 +32,16 @@ export default function SidebarToggleButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonWithIcon
|
<ButtonWithIcon
|
||||||
className={cn(className, 'z-[100]')}
|
className={cn(className, 'z-50')}
|
||||||
icon={<SidebarIcon size={20} />}
|
icon={<SidebarIcon size={20} />}
|
||||||
variant={'ghost'}
|
variant={'ghost'}
|
||||||
onClick={toggleSidebar}
|
onClick={toggleSidebar}
|
||||||
></ButtonWithIcon>
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MobileSidebarToggle({ isSidebarOpen }) {
|
||||||
|
return (
|
||||||
|
<SidebarToggleButton className={`absolute top-0 ${isSidebarOpen ? "right-0" : "left-0"} right-0 m-4`} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,79 +1,56 @@
|
|||||||
import { useIsMobile } from '@/hooks/use-is-mobile';
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
desktopSidebarAtom,
|
desktopSidebarAtom,
|
||||||
mobileSidebarAtom,
|
mobileSidebarAtom,
|
||||||
} from '@/components/sidebar/atoms/sidebar-atom';
|
} from "@/components/sidebar/atoms/sidebar-atom";
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { MobileSidebarToggle } from "./sidebar-toggle-button";
|
||||||
import { IconFileText } from '@tabler/icons-react';
|
import SettingsNav from "@/features/settings/nav/settings-nav";
|
||||||
import { SidebarSection } from '@/components/sidebar/sidebar-section';
|
import { usePathname } from "next/navigation";
|
||||||
import {
|
import React, { useEffect } from "react";
|
||||||
navigationMenu,
|
import Navigation from "@/components/sidebar/navigation/navigation";
|
||||||
NavigationMenuType,
|
|
||||||
} from '@/components/sidebar/actions/sidebar-actions';
|
|
||||||
import ButtonWithIcon from '@/components/ui/button-with-icon';
|
|
||||||
import SidebarToggleButton from './sidebar-toggle-button';
|
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [isSidebarOpen, setIsSidebarOpen] = useAtom(isMobile ? mobileSidebarAtom : desktopSidebarAtom);
|
||||||
|
const isSettings = pathname.startsWith("/settings");
|
||||||
|
|
||||||
const [isSidebarOpen] = useAtom(
|
const mobileClass = "fixed top-0 left-0 h-screen z-50 bg-background";
|
||||||
isMobile ? mobileSidebarAtom : desktopSidebarAtom
|
const sidebarWidth = isSidebarOpen ? "w-[270px]" : "w-[0px]";
|
||||||
);
|
|
||||||
|
const closeSidebar = () => {
|
||||||
|
setIsSidebarOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMobile) {
|
||||||
|
setIsSidebarOpen(false);
|
||||||
|
}
|
||||||
|
}, [pathname, isMobile, setIsSidebarOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<>
|
||||||
className={`${
|
{isMobile && isSidebarOpen && (
|
||||||
isSidebarOpen ? (isMobile ? 'w-full' : 'w-[270px]') : 'w-[0px]'
|
<div className="fixed top-0 left-0 w-full h-screen z-[50] bg-black/60"
|
||||||
} ${
|
onClick={closeSidebar}>
|
||||||
isMobile && isSidebarOpen
|
</div>
|
||||||
? 'fixed top-0 left-0 h-screen z-[99] bg-background'
|
|
||||||
: ''
|
|
||||||
} flex-grow-0 flex-shrink-0 overflow-hidden border-r duration-500 ease-in-out`}
|
|
||||||
>
|
|
||||||
{isMobile && (
|
|
||||||
<>
|
|
||||||
<SidebarToggleButton
|
|
||||||
className={`absolute top-0 ${
|
|
||||||
isSidebarOpen ? 'right-0' : 'left-0'
|
|
||||||
} right-0 m-4`}
|
|
||||||
/>
|
|
||||||
<div className="mt-[20px]"></div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`flex flex-col flex-shrink-0 gap-0.5 p-[10px]`}>
|
<nav
|
||||||
<div className="h-full">
|
className={`${sidebarWidth} ${isMobile && isSidebarOpen ? mobileClass : ""}
|
||||||
<div className="mt-[20px]"></div>
|
flex-grow-0 flex-shrink-0 overflow-hidden border-r duration-300 z-49`}>
|
||||||
|
|
||||||
<SidebarSection className="pb-2 mb-4 select-none border-b">
|
{isMobile && (
|
||||||
{navigationMenu.map((menu: NavigationMenuType, index: number) => (
|
<MobileSidebarToggle isSidebarOpen={isSidebarOpen} />
|
||||||
<ButtonWithIcon
|
)}
|
||||||
key={index}
|
|
||||||
icon={menu.icon}
|
|
||||||
variant={'ghost'}
|
|
||||||
className="w-full flex flex-1 justify-start items-center"
|
|
||||||
>
|
|
||||||
<span className="text-ellipsis overflow-hidden">
|
|
||||||
{menu.label}
|
|
||||||
</span>
|
|
||||||
</ButtonWithIcon>
|
|
||||||
))}
|
|
||||||
</SidebarSection>
|
|
||||||
|
|
||||||
<ScrollArea className="h-[70vh]">
|
<div className="flex flex-col flex-shrink-0 gap-0.5 p-[10px]">
|
||||||
<div className="space-y-1">
|
<div className="h-full mt-[8px]">
|
||||||
<ButtonWithIcon
|
{isSettings ? <SettingsNav /> : <Navigation />}
|
||||||
variant="ghost"
|
</div>
|
||||||
className="w-full justify-start"
|
|
||||||
icon={<IconFileText size={16} />}
|
|
||||||
>
|
|
||||||
Welcome page
|
|
||||||
</ButtonWithIcon>
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</nav>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,12 +7,13 @@ export default function TopBar() {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="max-w-full z-50 select-none">
|
<header className="max-w-full z-10 select-none">
|
||||||
<div className="w-full max-w-full h-[50px] opacity-100 relative duration-700 ease-in">
|
<div className="w-full max-w-full h-[50px] relative">
|
||||||
<div className="flex justify-between items-center h-full overflow-hidden py-0 px-1 gap-2.5 border-b">
|
<div className="flex justify-between items-center h-full overflow-hidden py-0 px-1 gap-2.5 border-b">
|
||||||
<div className="flex items-center leading-tight h-full flex-grow-0 mr-[8px] min-w-0 font-semibold text-sm">
|
<div className="flex items-center h-full flex-grow-0 mr-[8px] min-w-0">
|
||||||
{!isMobile && <SidebarToggleButton />}
|
{!isMobile && <SidebarToggleButton />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
50
frontend/src/features/settings/nav/settings-nav-items.tsx
Normal file
50
frontend/src/features/settings/nav/settings-nav-items.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { IconUserCircle, IconUser, IconUsers,
|
||||||
|
IconBuilding, IconSettingsCog } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
export interface SettingsNavMenuSection {
|
||||||
|
heading: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
items: SettingsNavMenuItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingsNavMenuItem {
|
||||||
|
label: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
target?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SettingsNavItem = SettingsNavMenuSection[];
|
||||||
|
|
||||||
|
export const settingsNavItems: SettingsNavItem = [
|
||||||
|
{
|
||||||
|
heading: 'Account',
|
||||||
|
icon: <IconUserCircle size={20}/>,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'My account',
|
||||||
|
icon: <IconUser size={16}/>,
|
||||||
|
target: '/settings/account',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: 'Workspace',
|
||||||
|
icon: <IconBuilding size={20}/>,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'General',
|
||||||
|
icon: <IconSettingsCog size={16}/>,
|
||||||
|
target: '/settings/workspace',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Members',
|
||||||
|
icon: <IconUsers size={16}/>,
|
||||||
|
target: '/settings/workspace/members',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
67
frontend/src/features/settings/nav/settings-nav.tsx
Normal file
67
frontend/src/features/settings/nav/settings-nav.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SettingsNavItem,
|
||||||
|
SettingsNavMenuItem, SettingsNavMenuSection,
|
||||||
|
settingsNavItems
|
||||||
|
} from "@/features/settings/nav/settings-nav-items";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
import { ChevronLeftIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
|
interface SettingsNavProps {
|
||||||
|
menu: SettingsNavItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RenderNavItem({ label, icon, target }: SettingsNavMenuItem): React.ReactNode {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const isActive = pathname === target;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ml-2">
|
||||||
|
<Link href={target} className={` ${isActive ? "bg-foreground/10 rounded-md" : ""}
|
||||||
|
w-full flex flex-1 justify-start items-center text-sm font-medium px-3 py-2`}>
|
||||||
|
<span className="mr-1">{icon}</span>
|
||||||
|
<span className="text-ellipsis overflow-hidden">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SettingsNavItems({ menu }: SettingsNavProps): React.ReactNode {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
href="/home"
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: "ghost" }),
|
||||||
|
"relative")} style={{marginLeft: '-5px', top:'-5px'}}>
|
||||||
|
<ChevronLeftIcon className="mr-2 h-4 w-4" /> Back
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-5 pt-0">
|
||||||
|
{menu.map((section: SettingsNavMenuSection, index: number) => (
|
||||||
|
<div key={index}>
|
||||||
|
<h3 className="flex items-center py-2 text-sm font-semibold text-muted-foreground">
|
||||||
|
<span className="mr-1">{section.icon}</span> {section.heading}
|
||||||
|
</h3>
|
||||||
|
{section.items.map((item: SettingsNavMenuItem, itemIndex: number) => (
|
||||||
|
<RenderNavItem key={itemIndex} {...item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SettingsNav() {
|
||||||
|
return <SettingsNavItems menu={settingsNavItems} />
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user