feat: sidebar, mobile sidebar, top nav
This commit is contained in:
parent
13af296b77
commit
8e7bad0092
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
|
@ -9,19 +9,34 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"lucide-react": "^0.367.0",
|
||||
"next": "14.1.4",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "14.1.4"
|
||||
"react-resizable-panels": "^2.0.16",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.4",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.4"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="border" >
|
||||
hello
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// import { SyncActiveOrgFromUrl } from "./sync-active-org-from-url";
|
||||
import { Dashboard } from "@/components/dashboard";
|
||||
import { TopNav } from "@/components/top-nav";
|
||||
import { topNavItems } from "@/config";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export default function WorkspaceLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: { workspaceId: string };
|
||||
}) {
|
||||
const layout = cookies().get("react-resizable-panels:layout");
|
||||
const collapsed = cookies().get("react-resizable-panels:collapsed");
|
||||
|
||||
const defaultLayout = layout?.value ? JSON.parse(layout.value) : undefined;
|
||||
const defaultCollapsed = collapsed?.value ? JSON.parse(collapsed.value) : undefined;
|
||||
|
||||
console.log("defaultL ", defaultLayout)
|
||||
console.log("defaultC ", defaultCollapsed)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* TODO: Nuke it when we can do it serverside in Clerk! */}
|
||||
{/* <SyncActiveOrgFromUrl /> */}
|
||||
<div className="min-h-screen rounded-[0.5rem]">
|
||||
<TopNav navItems={topNavItems}/>
|
||||
|
||||
<main className="min-h-[calc(100vh-53px)] flex-1 space-y-4">
|
||||
<Dashboard
|
||||
defaultLayout={defaultLayout}
|
||||
defaultCollapsed={defaultCollapsed}
|
||||
navCollapsedSize={4}
|
||||
>
|
||||
{props.children}
|
||||
</Dashboard>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -2,32 +2,58 @@
|
|||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Skilld Admin Dashboard",
|
||||
description: "Skilld Admin Dashboard",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
@ -16,7 +17,16 @@ export default function RootLayout({
|
|||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="dark"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
113
src/app/page.tsx
113
src/app/page.tsx
|
@ -1,113 +0,0 @@
|
|||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by editing
|
||||
<code className="font-mono font-bold">src/app/page.tsx</code>
|
||||
</p>
|
||||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
|
||||
<a
|
||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/vercel.svg"
|
||||
alt="Vercel Logo"
|
||||
className="dark:invert"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
|
||||
<Image
|
||||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js Logo"
|
||||
width={180}
|
||||
height={37}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Docs{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Find in-depth information about Next.js features and API.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Learn{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Learn about Next.js in an interactive course with quizzes!
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Templates{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Explore starter templates for Next.js.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Deploy{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
|
||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { HelpCircle, Settings } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { SidebarNav } from "./sidebar-nav";
|
||||
import { TopNav } from "./top-nav";
|
||||
import { docsConfig, topNavItems } from "@/config";
|
||||
import { DocsSidebarNav } from "./docs-sidebar-nav";
|
||||
// import { AddButton } from "@/components/buttons/AddButton";
|
||||
// import { AddAssetFlow } from "@/components/modals/add-asset-flow";
|
||||
// import { WorkspaceSwitcher } from "@/app/(dashboard)/_components/workspace-switcher";
|
||||
// import { SidebarNav } from "@/app/(dashboard)/(workspaceId)/_components/sidebar-nav";
|
||||
|
||||
// import { Nav } from "./nav";
|
||||
// import { CardsStats } from "./stats";
|
||||
// import { TopCategoriesTable } from "./top-categories-table";
|
||||
// import { TransactionsReviewTable } from "./transaction-review-table";
|
||||
|
||||
interface DashboardProps {
|
||||
defaultLayout: number[] | undefined;
|
||||
defaultCollapsed?: boolean;
|
||||
navCollapsedSize: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Dashboard({
|
||||
defaultLayout = [20, 40, 40],
|
||||
defaultCollapsed = false,
|
||||
navCollapsedSize,
|
||||
children,
|
||||
}: DashboardProps) {
|
||||
const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed);
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
onLayout={(sizes: number[]) => {
|
||||
document.cookie = `react-resizable-panels:layout=${JSON.stringify(
|
||||
sizes
|
||||
)}`;
|
||||
}}
|
||||
className="h-full items-stretch"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={defaultLayout[0]}
|
||||
collapsedSize={navCollapsedSize}
|
||||
collapsible={true}
|
||||
minSize={11}
|
||||
maxSize={12}
|
||||
onCollapse={() => {
|
||||
setIsCollapsed(true);
|
||||
document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(
|
||||
true
|
||||
)}`;
|
||||
}}
|
||||
onExpand={() => {
|
||||
setIsCollapsed(false);
|
||||
document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(
|
||||
false
|
||||
)}`;
|
||||
}}
|
||||
className={cn(
|
||||
isCollapsed &&
|
||||
"min-w-[50px] transition-all duration-300 ease-in-out",
|
||||
"hidden lg:block"
|
||||
)}
|
||||
>
|
||||
<SidebarNav isCollapsed={isCollapsed} />
|
||||
{/* <DocsSidebarNav items={docsConfig.sidebarNav} /> */}
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={defaultLayout[1]} minSize={30}>
|
||||
{/* <TopNav navItems={topNavItems}/> */}
|
||||
{children}
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
"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
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
|
||||
import { AlignJustify } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Icons } from "./icons";
|
||||
import { sideNavItems, siteConfig } from "@/config";
|
||||
import { ExpandedItem } from "./sidebar-nav";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export default function HamburgerMenu() {
|
||||
const params = useParams<{ workspaceId: string }>();
|
||||
const path = usePathname();
|
||||
|
||||
const pathname = path.replace(`/${params.workspaceId}`, "") || "/";
|
||||
const [_, currentPath] = pathname.split("/");
|
||||
|
||||
return (
|
||||
<div className="flex lg:hidden">
|
||||
<Sheet>
|
||||
<SheetTrigger>
|
||||
<AlignJustify />
|
||||
</SheetTrigger>
|
||||
<SheetContent side={"left"}>
|
||||
<SheetHeader>
|
||||
<SheetTitle className="flex items-center space-x-2">
|
||||
<Icons.logo />
|
||||
<span className="inline-block font-urban text-xl font-bold">
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
{sideNavItems.map((group, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<Separator className="mb-2 mt-2" />
|
||||
<div className="flex flex-col gap-1 p-2" key={group.group}>
|
||||
{group.items.map((link, idx) => {
|
||||
return (
|
||||
<ExpandedItem
|
||||
key={link.href + idx}
|
||||
item={link}
|
||||
currentPath={"/" + currentPath}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import {
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
BrainCircuit,
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
CreditCard,
|
||||
File,
|
||||
FileText,
|
||||
HelpCircle,
|
||||
Home,
|
||||
Image,
|
||||
Laptop,
|
||||
Loader2,
|
||||
LucideIcon,
|
||||
LucideProps,
|
||||
Moon,
|
||||
MoreVertical,
|
||||
PieChart,
|
||||
Plus,
|
||||
Puzzle,
|
||||
Search,
|
||||
Settings,
|
||||
SunMedium,
|
||||
Trash,
|
||||
User,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
|
||||
export type Icon = LucideIcon;
|
||||
|
||||
export const Icons = {
|
||||
add: Plus,
|
||||
arrowRight: ArrowRight,
|
||||
billing: CreditCard,
|
||||
chevronLeft: ChevronLeft,
|
||||
chevronRight: ChevronRight,
|
||||
check: Check,
|
||||
close: X,
|
||||
ellipsis: MoreVertical,
|
||||
help: HelpCircle,
|
||||
laptop: Laptop,
|
||||
logo: Puzzle,
|
||||
media: Image,
|
||||
moon: Moon,
|
||||
page: File,
|
||||
post: FileText,
|
||||
search: Search,
|
||||
settings: Settings,
|
||||
spinner: Loader2,
|
||||
sun: SunMedium,
|
||||
trash: Trash,
|
||||
user: User,
|
||||
warning: AlertTriangle,
|
||||
home: Home,
|
||||
piechart: PieChart,
|
||||
chevrondown: ChevronDown,
|
||||
brain: BrainCircuit,
|
||||
gitHub: ({ ...props }: LucideProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="github"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 496 512"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
google: ({ ...props }: LucideProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="google"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 488 512"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
|
||||
twitter: ({ ...props }: LucideProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="twitter"
|
||||
role="img"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M14.258 10.152L23.176 0h-2.113l-7.747 8.813L7.133 0H0l9.352 13.328L0 23.973h2.113l8.176-9.309 6.531 9.309h7.133zm-2.895 3.293l-.949-1.328L2.875 1.56h3.246l6.086 8.523.945 1.328 7.91 11.078h-3.246zm0 0"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
};
|
|
@ -0,0 +1,149 @@
|
|||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
// import { WorkspaceSwitcher } from "../../_components/workspace-switcher";
|
||||
import { NavItem, sideNavItems } from "@/config";
|
||||
import { Fragment } from "react";
|
||||
import { ScrollArea, ScrollBar } from "./ui/scroll-area";
|
||||
|
||||
const CollapsedItem = ({
|
||||
item,
|
||||
currentPath,
|
||||
}: {
|
||||
item: NavItem;
|
||||
currentPath: string;
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost", size: "icon" }),
|
||||
"h-9 w-9",
|
||||
currentPath === item.href
|
||||
? "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground"
|
||||
: "transparent",
|
||||
"soon" === item.badge && "cursor-not-allowed opacity-80"
|
||||
)}
|
||||
>
|
||||
{item.icon && <item.icon className="h-4 w-4" />}
|
||||
<span className="sr-only">{item.title}</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="flex items-center gap-4">
|
||||
{item.title}
|
||||
{item?.badge ? <Badge>Coming soon</Badge> : null}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const ExpandedItem = ({
|
||||
item,
|
||||
currentPath,
|
||||
}: {
|
||||
item: NavItem;
|
||||
currentPath: string;
|
||||
}) => {
|
||||
return (
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"justify-start",
|
||||
currentPath === item.href
|
||||
? "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground"
|
||||
: "transparent",
|
||||
// "soon" === item.badge && "cursor-not-allowed opacity-80"
|
||||
)}
|
||||
>
|
||||
{item.icon && <item.icon className="mr-2 h-4 w-4" />}
|
||||
{item.title}
|
||||
<span className={cn("ml-auto")}>
|
||||
{item?.badge && <Badge>{item.badge}</Badge>}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: idx not needed as key when all items have unique hrefs
|
||||
// also, the active link should be filtered by href and not idx
|
||||
export function SidebarNav({ isCollapsed }: { isCollapsed: boolean }) {
|
||||
console.log("collapsed ", isCollapsed);
|
||||
const params = useParams<{ workspaceId: string }>();
|
||||
const path = usePathname();
|
||||
|
||||
// remove the workspaceId from the path when comparing active links in sidebar
|
||||
const pathname = path.replace(`/${params.workspaceId}`, "") || "/";
|
||||
const [_, currentPath] = pathname.split("/");
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-[calc(100vh-53px)] flex-1">
|
||||
<nav
|
||||
className={cn("flex flex-col", {
|
||||
"items-center justify-center": isCollapsed,
|
||||
})}
|
||||
>
|
||||
{/* <div className="p-2">
|
||||
workspace
|
||||
<WorkspaceSwitcher isCollapsed={isCollapsed} />
|
||||
</div> */}
|
||||
|
||||
{sideNavItems.map((group, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
{/* <Separator /> */}
|
||||
<div
|
||||
className="flex flex-col gap-1 p-2"
|
||||
// key={group.group}
|
||||
// key={i}
|
||||
>
|
||||
{/* <div>{group.group}</div> */}
|
||||
{!isCollapsed ? (
|
||||
<h4 className="rounded-md px-2 mt-2 text-sm font-semibold">
|
||||
{group.group}
|
||||
</h4>
|
||||
) : (
|
||||
i !== 0 && <Separator />
|
||||
)}
|
||||
|
||||
{group.items.map((link, idx) => {
|
||||
return isCollapsed ? (
|
||||
<CollapsedItem
|
||||
// key={link.href + idx}
|
||||
|
||||
item={link}
|
||||
currentPath={"/" + currentPath}
|
||||
/>
|
||||
) : (
|
||||
<ExpandedItem
|
||||
// key={link.href + idx}
|
||||
key={idx}
|
||||
item={link}
|
||||
currentPath={"/" + currentPath}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<ScrollBar orientation="vertical" />
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
|
||||
export { ExpandedItem };
|
|
@ -0,0 +1,9 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "@radix-ui/react-icons"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import Link from "next/link";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { UserNav } from "@/components/user-nav";
|
||||
import { Icons } from "./icons";
|
||||
import { NavItem, siteConfig } from "@/config";
|
||||
import HamburgerMenu from "./hamburger-menu";
|
||||
|
||||
export const TopNav = ({ navItems }: { navItems: NavItem[] }) => {
|
||||
return (
|
||||
<nav className="flex h-[52px] items-center justify-between p-4 border-b">
|
||||
<div className="flex items-center gap-4">
|
||||
<HamburgerMenu />
|
||||
<h1 className="font-cal hidden text-xl font-semibold capitalize leading-none md:inline ml-4">
|
||||
{/* {props.title} */}
|
||||
Skilld
|
||||
</h1>
|
||||
{/* {props.breadcrumbItems && (
|
||||
<Breadcrumbs items={props.breadcrumbItems} />
|
||||
)} */}
|
||||
</div>
|
||||
{/* {props.headerAction} */}
|
||||
hello
|
||||
</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">
|
||||
// <Link href="/" className="flex items-center space-x-2">
|
||||
// <Icons.logo />
|
||||
// <span className="inline-block font-urban text-xl font-bold">
|
||||
// {siteConfig.name}
|
||||
// </span>
|
||||
// </Link>
|
||||
// <nav className="hidden gap-6 md:flex">
|
||||
// {navItems?.map((item, index) => (
|
||||
// <Link
|
||||
// key={index}
|
||||
// href={item.href}
|
||||
// className={cn(
|
||||
// "flex items-center text-lg font-medium text-foreground/60 transition-colors hover:text-foreground/80 sm:text-sm",
|
||||
// // TODO: active link css
|
||||
// // item.href.startsWith(`/${segment}`)
|
||||
// // ? "text-foreground"
|
||||
// // : "text-foreground/60",
|
||||
// )}
|
||||
// >
|
||||
// {item.title}
|
||||
// </Link>
|
||||
// ))}
|
||||
// </nav>
|
||||
// <div className="ml-auto flex items-center space-x-4">
|
||||
// {/* <Search /> */}
|
||||
// <UserNav />
|
||||
// </div>
|
||||
// </nav>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
|
@ -0,0 +1,36 @@
|
|||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
|
@ -0,0 +1,57 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
|
@ -0,0 +1,205 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
DotFilledIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
"use client"
|
||||
|
||||
import { DragHandleDots2Icon } from "@radix-ui/react-icons"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ResizablePanelGroup = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
|
||||
<ResizablePrimitive.PanelGroup
|
||||
className={cn(
|
||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
const ResizablePanel = ResizablePrimitive.Panel
|
||||
|
||||
const ResizableHandle = ({
|
||||
withHandle,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
||||
withHandle?: boolean
|
||||
}) => (
|
||||
<ResizablePrimitive.PanelResizeHandle
|
||||
className={cn(
|
||||
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{withHandle && (
|
||||
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
||||
<DragHandleDots2Icon className="h-2.5 w-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</ResizablePrimitive.PanelResizeHandle>
|
||||
)
|
||||
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
|
@ -0,0 +1,48 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
|
@ -0,0 +1,31 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
|
@ -0,0 +1,140 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger
|
||||
|
||||
const SheetClose = SheetPrimitive.Close
|
||||
|
||||
const SheetPortal = SheetPrimitive.Portal
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||
bottom:
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: "right",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
interface SheetContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(({ side = "right", className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetHeader.displayName = "SheetHeader"
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = "SheetFooter"
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetPortal,
|
||||
SheetOverlay,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@ -0,0 +1,108 @@
|
|||
import Link from "next/link";
|
||||
// import { currentUser } from "@clerk/nextjs";
|
||||
import {
|
||||
CreditCard,
|
||||
LayoutDashboard,
|
||||
LogOut,
|
||||
Settings,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
export async function UserNav() {
|
||||
// const user = await currentUser();
|
||||
|
||||
// if (!user) {
|
||||
// return (
|
||||
// <Link href="/signin">
|
||||
// <Button className="relative rounded-lg">Sign In</Button>
|
||||
// </Link>
|
||||
// );
|
||||
// }
|
||||
|
||||
// const fullname = `${user.firstName} ${user.lastName}`;
|
||||
// const initials = fullname
|
||||
// .split(" ")
|
||||
// .map((n) => n[0])
|
||||
// .join("");
|
||||
// const email = user.emailAddresses.find(
|
||||
// (e) => e.id === user.primaryEmailAddressId,
|
||||
// )?.emailAddress;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage src={""} alt={""} />
|
||||
<AvatarFallback>MH</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
{/* {user.firstName} {user.lastName} */}
|
||||
John Doe
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{/* {email} */}
|
||||
johndoe@gmail.com
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
{/* <DropdownMenuItem asChild>
|
||||
<Link href="/dashboard">
|
||||
<LayoutDashboard className="mr-2 h-4 w-4" />
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={`#`}>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem> */}
|
||||
{/* <DropdownMenuItem asChild>
|
||||
<Link href={`#`}>
|
||||
<CreditCard className="mr-2 h-4 w-4" />
|
||||
<span>Billing</span>
|
||||
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={`#`}>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem> */}
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="#">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>Log out</span>
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
// "use client";
|
||||
|
||||
// import * as React from "react";
|
||||
// import Link from "next/link";
|
||||
// import { useRouter } from "next/navigation";
|
||||
// import { api } from "@/trpc/client";
|
||||
// import { useOrganization, useOrganizationList, useUser } from "@clerk/nextjs";
|
||||
// import { toDecimal } from "dinero.js";
|
||||
// import { Check, ChevronDown, ChevronsUpDown, PlusCircle } from "lucide-react";
|
||||
|
||||
// import { env } from "@projectx/stripe/env";
|
||||
// import type { ExtendedPlanInfo, PlansResponse } from "@projectx/stripe/plans";
|
||||
// import type { PurchaseOrg } from "@projectx/validators";
|
||||
// import { purchaseOrgSchema } from "@projectx/validators";
|
||||
|
||||
// import { currencySymbol } from "@/lib/currency";
|
||||
// import { cn } from "@/lib/utils";
|
||||
// import { useZodForm } from "@/lib/zod-form";
|
||||
// import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import {
|
||||
// Command,
|
||||
// CommandGroup,
|
||||
// CommandInput,
|
||||
// CommandItem,
|
||||
// CommandList,
|
||||
// CommandSeparator,
|
||||
// } from "@/components/ui/command";
|
||||
// import {
|
||||
// Dialog,
|
||||
// DialogContent,
|
||||
// DialogDescription,
|
||||
// DialogFooter,
|
||||
// DialogHeader,
|
||||
// DialogTitle,
|
||||
// DialogTrigger,
|
||||
// } from "@/components/ui/dialog";
|
||||
// import {
|
||||
// Form,
|
||||
// FormControl,
|
||||
// FormField,
|
||||
// FormItem,
|
||||
// FormLabel,
|
||||
// FormMessage,
|
||||
// } from "@/components/ui/form";
|
||||
// import { Input } from "@/components/ui/input";
|
||||
// import {
|
||||
// Popover,
|
||||
// PopoverContent,
|
||||
// PopoverTrigger,
|
||||
// } from "@/components/ui/popover";
|
||||
// import {
|
||||
// Select,
|
||||
// SelectContent,
|
||||
// SelectItem,
|
||||
// SelectTrigger,
|
||||
// SelectValue,
|
||||
// } from "@/components/ui/select";
|
||||
// import { useToast } from "@/components/ui/use-toast";
|
||||
|
||||
// type WorkspaceSwitcherProps = {
|
||||
// isCollapsed: boolean;
|
||||
// };
|
||||
|
||||
// export function WorkspaceSwitcher({ isCollapsed }: WorkspaceSwitcherProps) {
|
||||
// const router = useRouter();
|
||||
|
||||
// const [switcherOpen, setSwitcherOpen] = React.useState(false);
|
||||
// const [newOrgDialogOpen, setNewOrgDialogOpen] = React.useState(false);
|
||||
|
||||
// const orgs = useOrganizationList({
|
||||
// userMemberships: {
|
||||
// infinite: true,
|
||||
// },
|
||||
// });
|
||||
// const org = useOrganization();
|
||||
|
||||
// const { user, isSignedIn, isLoaded } = useUser();
|
||||
// if (isLoaded && !isSignedIn) throw new Error("How did you get here???");
|
||||
|
||||
// const activeOrg = org.organization ?? user;
|
||||
// if (
|
||||
// !orgs.isLoaded ||
|
||||
// !org.isLoaded ||
|
||||
// !activeOrg ||
|
||||
// orgs.userMemberships.isLoading
|
||||
// ) {
|
||||
// // Skeleton loader
|
||||
// return (
|
||||
// <Button
|
||||
// variant="ghost"
|
||||
// size="sm"
|
||||
// role="combobox"
|
||||
// aria-expanded={switcherOpen}
|
||||
// aria-label="Select a workspace"
|
||||
// className="w-52 justify-between opacity-50"
|
||||
// >
|
||||
// <Avatar className="mr-2 h-5 w-5">
|
||||
// <AvatarFallback>Ac</AvatarFallback>
|
||||
// </Avatar>
|
||||
// <div className={cn("flex justify-between", isCollapsed && "hidden")}>
|
||||
// <span>Select a workspace</span>
|
||||
// <ChevronsUpDown className="ml-auto h-4 w-4 shrink-0" />
|
||||
// </div>
|
||||
// </Button>
|
||||
// );
|
||||
// }
|
||||
|
||||
// const normalizedObject = {
|
||||
// id: activeOrg.id,
|
||||
// name: "name" in activeOrg ? activeOrg.name : activeOrg.fullName,
|
||||
// image: activeOrg.imageUrl,
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Dialog open={newOrgDialogOpen} onOpenChange={setNewOrgDialogOpen}>
|
||||
// <Popover open={switcherOpen} onOpenChange={setSwitcherOpen}>
|
||||
// <PopoverTrigger asChild>
|
||||
// <Button
|
||||
// variant="ghost"
|
||||
// size="sm"
|
||||
// role="combobox"
|
||||
// aria-expanded={switcherOpen}
|
||||
// aria-label="Select a workspace"
|
||||
// className={cn(
|
||||
// "flex w-full items-center gap-2 border [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0",
|
||||
// isCollapsed &&
|
||||
// "flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden",
|
||||
// )}
|
||||
// >
|
||||
// <div className="flex justify-between">
|
||||
// <Avatar className={cn("h-5 w-5", !isCollapsed && "mr-2")}>
|
||||
// <AvatarImage src={normalizedObject?.image ?? ""} />
|
||||
// <AvatarFallback>
|
||||
// {normalizedObject.name?.substring(0, 2)}
|
||||
// </AvatarFallback>
|
||||
// </Avatar>
|
||||
// <span
|
||||
// className={cn("whitespace-nowrap", isCollapsed && "hidden")}
|
||||
// >
|
||||
// {normalizedObject.name}
|
||||
// </span>
|
||||
// </div>
|
||||
// <ChevronDown
|
||||
// className={cn(
|
||||
// "ml-auto h-4 w-4 shrink-0 opacity-50",
|
||||
// isCollapsed && "hidden",
|
||||
// )}
|
||||
// />
|
||||
// </Button>
|
||||
// </PopoverTrigger>
|
||||
// <PopoverContent className="ml-2 w-52 p-0">
|
||||
// <Command>
|
||||
// <CommandList>
|
||||
// <CommandInput placeholder="Search workspace..." />
|
||||
// <CommandGroup heading="Personal account">
|
||||
// <CommandItem
|
||||
// onSelect={async () => {
|
||||
// if (!user?.id) return;
|
||||
// normalizedObject.id = user.id ?? "";
|
||||
|
||||
// await orgs.setActive?.({ organization: null });
|
||||
// setSwitcherOpen(false);
|
||||
// router.push(`/${user.id}`);
|
||||
// }}
|
||||
// className="cursor-pointer text-sm"
|
||||
// >
|
||||
// <Avatar className="mr-2 h-5 w-5">
|
||||
// <AvatarImage
|
||||
// src={user?.imageUrl}
|
||||
// alt={user?.fullName ?? ""}
|
||||
// />
|
||||
// <AvatarFallback>
|
||||
// {`${user?.firstName?.[0]}${user?.lastName?.[0]}` ?? "JD"}
|
||||
// </AvatarFallback>
|
||||
// </Avatar>
|
||||
// {user?.fullName}
|
||||
// <Check
|
||||
// className={cn(
|
||||
// "ml-auto h-4 w-4",
|
||||
// org.organization === null ? "opacity-100" : "opacity-0",
|
||||
// )}
|
||||
// />
|
||||
// </CommandItem>
|
||||
// </CommandGroup>
|
||||
|
||||
// <CommandGroup heading="Organizations">
|
||||
// {orgs.userMemberships.data?.map(({ organization: org }) => (
|
||||
// <CommandItem
|
||||
// key={org.name}
|
||||
// onSelect={async () => {
|
||||
// await orgs.setActive({ organization: org });
|
||||
// setSwitcherOpen(false);
|
||||
// router.push(`/${org.id}`);
|
||||
// }}
|
||||
// className="cursor-pointer text-sm"
|
||||
// >
|
||||
// <Avatar className="mr-2 h-5 w-5">
|
||||
// <AvatarImage
|
||||
// src={org.imageUrl ?? "/images/placeholder.png"}
|
||||
// alt={org.name}
|
||||
// />
|
||||
// <AvatarFallback>
|
||||
// {org.name.substring(0, 2)}
|
||||
// </AvatarFallback>
|
||||
// </Avatar>
|
||||
// {org.name}
|
||||
// <Check
|
||||
// className={cn(
|
||||
// "ml-auto h-4 w-4",
|
||||
// normalizedObject?.id === org.id
|
||||
// ? "opacity-100"
|
||||
// : "opacity-0",
|
||||
// )}
|
||||
// />
|
||||
// </CommandItem>
|
||||
// ))}
|
||||
// </CommandGroup>
|
||||
// </CommandList>
|
||||
// <CommandSeparator />
|
||||
// <CommandList>
|
||||
// <CommandGroup>
|
||||
// <DialogTrigger asChild>
|
||||
// <CommandItem
|
||||
// onSelect={() => {
|
||||
// setSwitcherOpen(false);
|
||||
// setNewOrgDialogOpen(true);
|
||||
// }}
|
||||
// className="cursor-pointer"
|
||||
// >
|
||||
// <PlusCircle className="mr-2 h-5 w-5" />
|
||||
// Create Organization
|
||||
// </CommandItem>
|
||||
// </DialogTrigger>
|
||||
// </CommandGroup>
|
||||
// </CommandList>
|
||||
// </Command>
|
||||
// </PopoverContent>
|
||||
// </Popover>
|
||||
|
||||
// <React.Suspense>
|
||||
// <NewOrganizationDialog closeDialog={() => setNewOrgDialogOpen(false)} />
|
||||
// </React.Suspense>
|
||||
// </Dialog>
|
||||
// );
|
||||
// }
|
||||
|
||||
// function NewOrganizationDialog(props: { closeDialog: () => void }) {
|
||||
// const useStripe = env.USE_STRIPE === "true";
|
||||
|
||||
// let plans: any | null = null;
|
||||
// if (useStripe) {
|
||||
// plans = api.stripe.plans.query();
|
||||
// }
|
||||
|
||||
// const form = useZodForm({ schema: purchaseOrgSchema });
|
||||
|
||||
// const toaster = useToast();
|
||||
|
||||
// async function handleCreateOrg(data: PurchaseOrg) {
|
||||
// const response = await api.stripe.purchaseOrg
|
||||
// .mutate(data)
|
||||
// .catch(() => ({ success: false as const }));
|
||||
|
||||
// if (response?.success) window.location.href = response.url as string;
|
||||
// else
|
||||
// toaster.toast({
|
||||
// title: "Error",
|
||||
// description:
|
||||
// "There was an error setting up your organization. Please try again.",
|
||||
// variant: "destructive",
|
||||
// });
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <DialogContent>
|
||||
// <Form {...form}>
|
||||
// <form
|
||||
// onSubmit={form.handleSubmit(handleCreateOrg)}
|
||||
// className="space-y-4"
|
||||
// >
|
||||
// <DialogHeader>
|
||||
// <DialogTitle>Create organization</DialogTitle>
|
||||
// <DialogDescription>
|
||||
// Add a new organization to manage products and customers.
|
||||
// </DialogDescription>
|
||||
// </DialogHeader>
|
||||
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="orgName"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Organization name *</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="Acme Inc." />
|
||||
// </FormControl>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
|
||||
// {useStripe && (
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="planId"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <div className="flex justify-between">
|
||||
// <FormLabel>Subscription plan *</FormLabel>
|
||||
// <Link
|
||||
// href="/pricing"
|
||||
// className="text-xs text-muted-foreground hover:underline"
|
||||
// >
|
||||
// What's included in each plan?
|
||||
// </Link>
|
||||
// </div>
|
||||
// <Select
|
||||
// onValueChange={field.onChange}
|
||||
// defaultValue={field.value}
|
||||
// >
|
||||
// <FormControl>
|
||||
// <SelectTrigger>
|
||||
// <SelectValue placeholder="Select a plan" />
|
||||
// </SelectTrigger>
|
||||
// </FormControl>
|
||||
// <SelectContent>
|
||||
// {plans?.map((plan: ExtendedPlanInfo) => (
|
||||
// <SelectItem key={plan.priceId} value={plan.priceId}>
|
||||
// <span className="font-medium">{plan.name}</span> -{" "}
|
||||
// <span className="text-muted-foreground">
|
||||
// {toDecimal(
|
||||
// plan.price,
|
||||
// ({ value, currency }) =>
|
||||
// `${currencySymbol(currency.code)}${value}`,
|
||||
// )}{" "}
|
||||
// per month
|
||||
// </span>
|
||||
// </SelectItem>
|
||||
// ))}
|
||||
// </SelectContent>
|
||||
// </Select>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// )}
|
||||
|
||||
// <DialogFooter>
|
||||
// <Button variant="outline" onClick={() => props.closeDialog()}>
|
||||
// Cancel
|
||||
// </Button>
|
||||
// <Button type="submit">Continue</Button>
|
||||
// </DialogFooter>
|
||||
// </form>
|
||||
// </Form>
|
||||
// </DialogContent>
|
||||
// );
|
||||
// }
|
|
@ -0,0 +1,498 @@
|
|||
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: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { Icons } from "@/components/icons";
|
||||
|
||||
export interface NavItem {
|
||||
title: string;
|
||||
href?: string;
|
||||
disabled?: boolean;
|
||||
external?: boolean;
|
||||
icon?: keyof typeof Icons;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface NavItemWithChildren extends NavItem {
|
||||
items: NavItemWithChildren[];
|
||||
}
|
||||
|
||||
export interface MainNavItem extends NavItem {}
|
||||
|
||||
export interface SidebarNavItem extends NavItemWithChildren {}
|
|
@ -1,20 +1,80 @@
|
|||
import type { Config } from "tailwindcss";
|
||||
import type { Config } from "tailwindcss"
|
||||
|
||||
const config: Config = {
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
export default config;
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
} satisfies Config
|
||||
|
||||
export default config
|
Loading…
Reference in New Issue