feat: scrollable mobile sidebar

This commit is contained in:
mehedi-hasan 2024-04-12 17:44:19 +06:00
parent 8e7bad0092
commit e331ae377c
10 changed files with 173 additions and 643 deletions

View File

@ -1,36 +0,0 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -1,7 +1,6 @@
// import { SyncActiveOrgFromUrl } from "./sync-active-org-from-url"; // import { SyncActiveOrgFromUrl } from "./sync-active-org-from-url";
import { Dashboard } from "@/components/dashboard"; import { Dashboard } from "@/components/dashboard";
import { TopNav } from "@/components/top-nav"; import { TopNav } from "@/components/top-nav";
import { topNavItems } from "@/config";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
export default function WorkspaceLayout(props: { export default function WorkspaceLayout(props: {
@ -22,7 +21,7 @@ export default function WorkspaceLayout(props: {
{/* TODO: Nuke it when we can do it serverside in Clerk! */} {/* TODO: Nuke it when we can do it serverside in Clerk! */}
{/* <SyncActiveOrgFromUrl /> */} {/* <SyncActiveOrgFromUrl /> */}
<div className="min-h-screen rounded-[0.5rem]"> <div className="min-h-screen rounded-[0.5rem]">
<TopNav navItems={topNavItems}/> <TopNav/>
<main className="min-h-[calc(100vh-53px)] flex-1 space-y-4"> <main className="min-h-[calc(100vh-53px)] flex-1 space-y-4">
<Dashboard <Dashboard

View File

@ -1,84 +0,0 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
import { SidebarNavItem } from "@/types/nav"
export interface DocsSidebarNavProps {
items: SidebarNavItem[]
}
export function DocsSidebarNav({ items }: DocsSidebarNavProps) {
const pathname = usePathname()
return items.length ? (
<div className="w-full">
{items.map((item, index) => (
<div key={index} className={cn("pb-4")}>
<h4 className="mb-1 rounded-md px-2 py-1 text-sm font-semibold">
{item.title}
</h4>
{item?.items?.length && (
<DocsSidebarNavItems items={item.items} pathname={pathname} />
)}
</div>
))}
</div>
) : null
}
interface DocsSidebarNavItemsProps {
items: SidebarNavItem[]
pathname: string | null
}
export function DocsSidebarNavItems({
items,
pathname,
}: DocsSidebarNavItemsProps) {
return items?.length ? (
<div className="grid grid-flow-row auto-rows-max text-sm">
{items.map((item, index) =>
item.href && !item.disabled ? (
<Link
key={index}
href={item.href}
className={cn(
"group flex w-full items-center rounded-md border border-transparent px-2 py-1 hover:underline",
item.disabled && "cursor-not-allowed opacity-60",
pathname === item.href
? "font-medium text-foreground"
: "text-muted-foreground"
)}
target={item.external ? "_blank" : ""}
rel={item.external ? "noreferrer" : ""}
>
{item.title}
{item.label && (
<span className="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{item.label}
</span>
)}
</Link>
) : (
<span
key={index}
className={cn(
"flex w-full cursor-not-allowed items-center rounded-md p-2 text-muted-foreground hover:underline",
item.disabled && "cursor-not-allowed opacity-60"
)}
>
{item.title}
{item.label && (
<span className="ml-2 rounded-md bg-muted px-1.5 py-0.5 text-xs leading-none text-muted-foreground no-underline group-hover:no-underline">
{item.label}
</span>
)}
</span>
)
)}
</div>
) : null
}

View File

@ -3,21 +3,21 @@
import { import {
Sheet, Sheet,
SheetContent, SheetContent,
SheetDescription,
SheetHeader, SheetHeader,
SheetTitle, SheetTitle,
SheetTrigger, SheetTrigger,
} from "@/components/ui/sheet"; } from "@/components/ui/sheet";
import { AlignJustify } from "lucide-react"; import { AlignJustify } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { useParams, usePathname } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Icons } from "./icons"; import { Icons } from "./icons";
import { sideNavItems, siteConfig } from "@/config";
import { ExpandedItem } from "./sidebar-nav"; import { ExpandedItem } from "./sidebar-nav";
import { Fragment } from "react"; import { Fragment } from "react";
import { sideNavItems } from "@/config/nav";
import { siteConfig } from "@/config/site";
import { buttonVariants } from "./ui/button";
import { ScrollArea, ScrollBar } from "./ui/scroll-area";
export default function HamburgerMenu() { export default function HamburgerMenu() {
const params = useParams<{ workspaceId: string }>(); const params = useParams<{ workspaceId: string }>();
@ -29,10 +29,13 @@ export default function HamburgerMenu() {
return ( return (
<div className="flex lg:hidden"> <div className="flex lg:hidden">
<Sheet> <Sheet>
<SheetTrigger> <SheetTrigger
className={buttonVariants({ variant: "outline", size: "icon" })}
>
<AlignJustify /> <AlignJustify />
</SheetTrigger> </SheetTrigger>
<SheetContent side={"left"}> <SheetContent side={"left"} className="p-0">
<ScrollArea className="h-screen border p-4">
<SheetHeader> <SheetHeader>
<SheetTitle className="flex items-center space-x-2"> <SheetTitle className="flex items-center space-x-2">
<Icons.logo /> <Icons.logo />
@ -41,7 +44,8 @@ export default function HamburgerMenu() {
</span> </span>
</SheetTitle> </SheetTitle>
</SheetHeader> </SheetHeader>
{sideNavItems.map((group, i) => { {sideNavItems.map((group, i) => {
return ( return (
<Fragment key={i}> <Fragment key={i}>
@ -60,6 +64,8 @@ export default function HamburgerMenu() {
</Fragment> </Fragment>
); );
})} })}
<ScrollBar orientation="vertical"/>
</ScrollArea>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
</div> </div>

View File

@ -12,9 +12,9 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
// import { WorkspaceSwitcher } from "../../_components/workspace-switcher"; // import { WorkspaceSwitcher } from "../../_components/workspace-switcher";
import { NavItem, sideNavItems } from "@/config";
import { Fragment } from "react"; import { Fragment } from "react";
import { ScrollArea, ScrollBar } from "./ui/scroll-area"; import { ScrollArea, ScrollBar } from "./ui/scroll-area";
import { NavItem, sideNavItems } from "@/config/nav";
const CollapsedItem = ({ const CollapsedItem = ({
item, item,
@ -61,7 +61,7 @@ const ExpandedItem = ({
href={item.href} href={item.href}
className={cn( className={cn(
buttonVariants({ variant: "ghost" }), buttonVariants({ variant: "ghost" }),
"justify-start", "justify-start text-muted-foreground",
currentPath === item.href currentPath === item.href
? "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground" ? "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground"
: "transparent", : "transparent",

View File

@ -1,26 +1,26 @@
import Link from "next/link";
import { cn } from "@/lib/utils";
import { UserNav } from "@/components/user-nav"; import { UserNav } from "@/components/user-nav";
import { Icons } from "./icons";
import { NavItem, siteConfig } from "@/config";
import HamburgerMenu from "./hamburger-menu"; import HamburgerMenu from "./hamburger-menu";
import { ThemeToggle } from "./theme-toggle";
import { NavItem } from "@/config/nav";
import { siteConfig } from "@/config/site";
export const TopNav = ({ navItems }: { navItems: NavItem[] }) => { export const TopNav = () => {
return ( return (
<nav className="flex h-[52px] items-center justify-between p-4 border-b"> <nav className="flex h-[52px] items-center justify-between py-4 px-6 border-b">
<div className="flex items-center gap-4"> <div>
<HamburgerMenu /> <HamburgerMenu />
<h1 className="font-cal hidden text-xl font-semibold capitalize leading-none md:inline ml-4"> <h1 className="font-cal hidden text-xl font-semibold capitalize leading-none md:inline ml-4">
{/* {props.title} */} {siteConfig.name}
Skilld
</h1> </h1>
{/* {props.breadcrumbItems && ( {/* {props.breadcrumbItems && (
<Breadcrumbs items={props.breadcrumbItems} /> <Breadcrumbs items={props.breadcrumbItems} />
)} */} )} */}
</div> </div>
{/* {props.headerAction} */} {/* {props.headerAction} */}
hello <div className="flex items-center gap-4">
<ThemeToggle />
<UserNav />
</div>
</nav> </nav>
// <nav className="sticky top-0 z-10 flex h-16 items-center gap-10 border-b bg-background/60 px-4 backdrop-blur-xl transition-all"> // <nav className="sticky top-0 z-10 flex h-16 items-center gap-10 border-b bg-background/60 px-4 backdrop-blur-xl transition-all">
// <Link href="/" className="flex items-center space-x-2"> // <Link href="/" className="flex items-center space-x-2">

View File

@ -64,8 +64,8 @@ export async function UserNav() {
</p> </p>
</div> </div>
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> {/* <DropdownMenuSeparator /> */}
<DropdownMenuGroup> {/* <DropdownMenuGroup> */}
{/* <DropdownMenuItem asChild> {/* <DropdownMenuItem asChild>
<Link href="/dashboard"> <Link href="/dashboard">
<LayoutDashboard className="mr-2 h-4 w-4" /> <LayoutDashboard className="mr-2 h-4 w-4" />
@ -93,7 +93,7 @@ export async function UserNav() {
<DropdownMenuShortcut>S</DropdownMenuShortcut> <DropdownMenuShortcut>S</DropdownMenuShortcut>
</Link> </Link>
</DropdownMenuItem> */} </DropdownMenuItem> */}
</DropdownMenuGroup> {/* </DropdownMenuGroup> */}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link href="#"> <Link href="#">

View File

@ -1,498 +0,0 @@
import type { Route } from "next";
import {
BarChart,
BarChart4,
Bolt,
BookA,
BookText,
Building,
CircleUser,
Cloudy,
CreditCard,
FlaskConical,
FlaskRound,
GitCommitHorizontal,
HelpCircle,
Key,
Layers,
LayoutDashboard,
LucideIcon,
Ruler,
Settings,
Sparkle,
Sparkles,
Sprout,
Star,
Users,
Wrench,
} from "lucide-react";
import { MainNavItem, SidebarNavItem } from "./types/nav";
export type NavItem = {
href: Route;
title: string;
badge?: string;
icon?: LucideIcon;
};
export type GroupedNavItems = {
group: string;
items: NavItem[];
};
export const sideNavItems: GroupedNavItems[] = [
{
group: "EVALUATION",
items: [
{
href: "/new-analysis",
title: "Create New Analysis",
icon: FlaskConical,
},
{
href: "/analysis",
title: "Analysis",
icon: FlaskRound,
},
],
},
{
group: "COURSE",
items: [
{
href: "/course",
title: "Your Course",
// badge: "soon",
icon: BookText,
},
],
},
{
group: "ADMIN",
items: [
{
href: "/users",
title: "Users",
icon: Users,
},
{
href: "/organization",
title: "Organization",
icon: Building,
},
{
href: "/account",
title: "Account",
icon: CircleUser,
},
{
href: "/preferences",
title: "Preferences",
icon: Bolt,
},
{
href: "/configuration",
title: "Configuration",
icon: Wrench,
},
{
href: "/api-communication",
title: "Api Communication",
icon: Cloudy,
},
{
href: "/course-admin",
title: "Course Admin",
icon: BookA,
},
],
},
{
group: "ACCOUNT",
items: [
{
href: "/change-password",
title: "Change Password",
icon: Key,
},
],
},
{
group: "EVALUATIONS",
items: [
{
href: "/criteria",
title: "Criteria",
// badge: "soon",
icon: Ruler,
},
{
href: "/skill",
title: "Skill",
// badge: "soon",
icon: Sparkles,
},
{
href: "/evaluation",
title: "Evaluation",
// badge: "soon",
icon: BarChart4,
},
{
href: "/commit",
title: "Commit",
// badge: "soon",
icon: GitCommitHorizontal,
},
],
},
];
export const siteConfig = {
name: "Skilld Admin",
description:
"Empower your financial management with AI-driven insights, making tracking and optimizing your finances effortless.",
github: "https://github.com/projectx-codehagen/Badget",
twitter: "https://twitter.com/codehagen",
};
export const topNavItems = [
{
href: "/docs",
title: "Documentation",
},
{
href: "/support",
title: "Support",
},
] satisfies NavItem[];
interface DocsConfig {
mainNav: MainNavItem[];
sidebarNav: SidebarNavItem[];
}
export const docsConfig: DocsConfig = {
mainNav: [
{
title: "Documentation",
href: "/docs",
},
{
title: "Components",
href: "/docs/components/accordion",
},
{
title: "Themes",
href: "/themes",
},
{
title: "Examples",
href: "/examples",
},
{
title: "Blocks",
href: "/blocks",
},
],
sidebarNav: [
{
title: "Getting Started",
items: [
{
title: "Introduction",
href: "/docs",
items: [],
},
{
title: "Installation",
href: "/docs/installation",
items: [],
},
{
title: "components.json",
href: "/docs/components-json",
items: [],
},
{
title: "Theming",
href: "/docs/theming",
items: [],
},
{
title: "Dark mode",
href: "/docs/dark-mode",
items: [],
},
{
title: "CLI",
href: "/docs/cli",
items: [],
},
{
title: "Typography",
href: "/docs/components/typography",
items: [],
},
{
title: "Figma",
href: "/docs/figma",
items: [],
},
{
title: "Changelog",
href: "/docs/changelog",
items: [],
},
],
},
{
title: "Components",
items: [
{
title: "Accordion",
href: "/docs/components/accordion",
items: [],
},
{
title: "Alert",
href: "/docs/components/alert",
items: [],
},
{
title: "Alert Dialog",
href: "/docs/components/alert-dialog",
items: [],
},
{
title: "Aspect Ratio",
href: "/docs/components/aspect-ratio",
items: [],
},
{
title: "Avatar",
href: "/docs/components/avatar",
items: [],
},
{
title: "Badge",
href: "/docs/components/badge",
items: [],
},
{
title: "Breadcrumb",
href: "/docs/components/breadcrumb",
items: [],
label: "New",
},
{
title: "Button",
href: "/docs/components/button",
items: [],
},
{
title: "Calendar",
href: "/docs/components/calendar",
items: [],
},
{
title: "Card",
href: "/docs/components/card",
items: [],
},
{
title: "Carousel",
href: "/docs/components/carousel",
items: [],
},
{
title: "Checkbox",
href: "/docs/components/checkbox",
items: [],
},
{
title: "Collapsible",
href: "/docs/components/collapsible",
items: [],
},
{
title: "Combobox",
href: "/docs/components/combobox",
items: [],
},
{
title: "Command",
href: "/docs/components/command",
items: [],
},
{
title: "Context Menu",
href: "/docs/components/context-menu",
items: [],
},
{
title: "Data Table",
href: "/docs/components/data-table",
items: [],
},
{
title: "Date Picker",
href: "/docs/components/date-picker",
items: [],
},
{
title: "Dialog",
href: "/docs/components/dialog",
items: [],
},
{
title: "Drawer",
href: "/docs/components/drawer",
items: [],
},
{
title: "Dropdown Menu",
href: "/docs/components/dropdown-menu",
items: [],
},
{
title: "Form",
href: "/docs/components/form",
items: [],
},
{
title: "Hover Card",
href: "/docs/components/hover-card",
items: [],
},
{
title: "Input",
href: "/docs/components/input",
items: [],
},
{
title: "Input OTP",
href: "/docs/components/input-otp",
items: [],
label: "New",
},
{
title: "Label",
href: "/docs/components/label",
items: [],
},
{
title: "Menubar",
href: "/docs/components/menubar",
items: [],
},
{
title: "Navigation Menu",
href: "/docs/components/navigation-menu",
items: [],
},
{
title: "Pagination",
href: "/docs/components/pagination",
items: [],
},
{
title: "Popover",
href: "/docs/components/popover",
items: [],
},
{
title: "Progress",
href: "/docs/components/progress",
items: [],
},
{
title: "Radio Group",
href: "/docs/components/radio-group",
items: [],
},
{
title: "Resizable",
href: "/docs/components/resizable",
items: [],
},
{
title: "Scroll Area",
href: "/docs/components/scroll-area",
items: [],
},
{
title: "Select",
href: "/docs/components/select",
items: [],
},
{
title: "Separator",
href: "/docs/components/separator",
items: [],
},
{
title: "Sheet",
href: "/docs/components/sheet",
items: [],
},
{
title: "Skeleton",
href: "/docs/components/skeleton",
items: [],
},
{
title: "Slider",
href: "/docs/components/slider",
items: [],
},
{
title: "Sonner",
href: "/docs/components/sonner",
items: [],
},
{
title: "Switch",
href: "/docs/components/switch",
items: [],
},
{
title: "Table",
href: "/docs/components/table",
items: [],
},
{
title: "Tabs",
href: "/docs/components/tabs",
items: [],
},
{
title: "Textarea",
href: "/docs/components/textarea",
items: [],
},
{
title: "Toast",
href: "/docs/components/toast",
items: [],
},
{
title: "Toggle",
href: "/docs/components/toggle",
items: [],
},
{
title: "Toggle Group",
href: "/docs/components/toggle-group",
items: [],
},
{
title: "Tooltip",
href: "/docs/components/tooltip",
items: [],
},
],
},
],
};

137
src/config/nav.ts Normal file
View File

@ -0,0 +1,137 @@
import type { Route } from "next";
import {
BarChart4,
Bolt,
BookA,
BookText,
Building,
CircleUser,
Cloudy,
FlaskConical,
FlaskRound,
GitCommitHorizontal,
Key,
LucideIcon,
Ruler,
Sparkles,
Users,
Wrench,
} from "lucide-react";
export type NavItem = {
href: Route;
title: string;
badge?: string;
icon?: LucideIcon;
};
export type GroupedNavItems = {
group: string;
items: NavItem[];
};
export const sideNavItems: GroupedNavItems[] = [
{
group: "EVALUATION",
items: [
{
href: "/new-analysis",
title: "Create New Analysis",
icon: FlaskConical,
},
{
href: "/analysis",
title: "Analysis",
icon: FlaskRound,
},
],
},
{
group: "COURSE",
items: [
{
href: "/course",
title: "Your Course",
icon: BookText,
},
],
},
{
group: "ADMIN",
items: [
{
href: "/users",
title: "Users",
icon: Users,
},
{
href: "/organization",
title: "Organization",
icon: Building,
},
{
href: "/account",
title: "Account",
icon: CircleUser,
},
{
href: "/preferences",
title: "Preferences",
icon: Bolt,
},
{
href: "/configuration",
title: "Configuration",
icon: Wrench,
},
{
href: "/api-communication",
title: "Api Communication",
icon: Cloudy,
},
{
href: "/course-admin",
title: "Course Admin",
icon: BookA,
},
],
},
{
group: "ACCOUNT",
items: [
{
href: "/change-password",
title: "Change Password",
icon: Key,
},
],
},
{
group: "EVALUATIONS",
items: [
{
href: "/criteria",
title: "Criteria",
icon: Ruler,
},
{
href: "/skill",
title: "Skill",
icon: Sparkles,
},
{
href: "/evaluation",
title: "Evaluation",
icon: BarChart4,
},
{
href: "/commit",
title: "Commit",
icon: GitCommitHorizontal,
},
],
},
];

6
src/config/site.ts Normal file
View File

@ -0,0 +1,6 @@
export const siteConfig = {
name: "Skilld Admin",
description:
"Skilld Admin description",
};