Преглед на файлове

feat(header): enhance Header component with user menu and mobile navigation
feat(package): add @radix-ui/react-dialog dependency
feat(sheet): implement Sheet component for dialog functionality

vtugulan преди 6 месеца
родител
ревизия
3e69443336
променени са 4 файла, в които са добавени 385 реда и са изтрити 38 реда
  1. 243 38
      app/components/header.tsx
  2. 140 0
      components/ui/sheet.tsx
  3. 1 0
      package-lock.json
  4. 1 0
      package.json

+ 243 - 38
app/components/header.tsx

@@ -2,52 +2,257 @@
 
 import Link from "next/link";
 import Image from "next/image";
-import { LoginLink, LogoutLink } from "@kinde-oss/kinde-auth-nextjs/server";
+import { LoginLink, LogoutLink, RegisterLink } from "@kinde-oss/kinde-auth-nextjs/server";
 import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";
+import {
+  NavigationMenu,
+  NavigationMenuContent,
+  NavigationMenuItem,
+  NavigationMenuLink,
+  NavigationMenuList,
+  NavigationMenuTrigger,
+} from "@/components/ui/navigation-menu";
+import { Button } from "@/components/ui/button";
+import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
+import { Menu, FileText, Upload, Home, Folder, Settings, User, LogOut, LogIn } from "lucide-react";
+import { cn } from "@/lib/utils";
+import React from "react";
+
+const navigationItems = [
+  {
+    title: "Files",
+    href: "/files",
+    description: "Browse and manage your files",
+    icon: Folder,
+  },
+  {
+    title: "Upload",
+    href: "/upload",
+    description: "Upload new files to the platform",
+    icon: Upload,
+  },
+  {
+    title: "API Docs",
+    href: "/api-docs",
+    description: "View API documentation",
+    icon: FileText,
+  },
+];
+
+const userMenuItems = [
+  {
+    title: "Profile",
+    href: "/profile",
+    description: "Manage your profile settings",
+    icon: User,
+  },
+  {
+    title: "Settings",
+    href: "/settings",
+    description: "Configure your preferences",
+    icon: Settings,
+  },
+];
 
 export default function Header() {
   const { isAuthenticated, getUser } = useKindeBrowserClient();
   const user = getUser();
 
   return (
-    <header className="border-b border-gray-200 bg-white">
-      <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
-        <div className="flex justify-between items-center py-4">
-          <div className="flex items-center">
-            <Link href="/" className="flex items-center">
-              <img
-                src="/vtorio.svg"
-                alt="vtor.io"
-                className="h-8 w-auto"
-              />
-            </Link>
-          </div>
-          <div className="flex items-center space-x-4">
-            {!isAuthenticated && 
-              <LoginLink
-                className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5">
-                Login
+    <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
+      <div className="container flex h-16 items-center justify-between">
+        <div className="flex items-center gap-6">
+          <Link href="/" className="flex items-center space-x-2">
+            <Image
+              src="/vtorio.svg"
+              alt="vtor.io"
+              width={32}
+              height={32}
+              className="h-8 w-auto"
+            />
+            <span className="font-bold text-xl hidden sm:inline">vtor.io</span>
+          </Link>
+          
+          <NavigationMenu className="hidden md:flex">
+            <NavigationMenuList>
+              <NavigationMenuItem>
+                <Link href="/" legacyBehavior passHref>
+                  <NavigationMenuLink className={cn(
+                    "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50"
+                  )}>
+                    <Home className="mr-2 h-4 w-4" />
+                    Home
+                  </NavigationMenuLink>
+                </Link>
+              </NavigationMenuItem>
+              
+              {navigationItems.map((item) => (
+                <NavigationMenuItem key={item.title}>
+                  <Link href={item.href} legacyBehavior passHref>
+                    <NavigationMenuLink className={cn(
+                      "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50"
+                    )}>
+                      <item.icon className="mr-2 h-4 w-4" />
+                      {item.title}
+                    </NavigationMenuLink>
+                  </Link>
+                </NavigationMenuItem>
+              ))}
+            </NavigationMenuList>
+          </NavigationMenu>
+        </div>
+
+        <div className="flex items-center gap-4">
+          {isAuthenticated ? (
+            <>
+              <NavigationMenu className="hidden md:flex">
+                <NavigationMenuList>
+                  <NavigationMenuItem>
+                    <NavigationMenuTrigger className="h-9">
+                      <div className="flex items-center gap-2">
+                        <Image
+                          className="rounded-full"
+                          src={user?.picture || "/default-avatar.png"}
+                          alt="User Avatar"
+                          width={24}
+                          height={24}
+                        />
+                        <span className="text-sm font-medium">{user?.given_name || user?.email}</span>
+                      </div>
+                    </NavigationMenuTrigger>
+                    <NavigationMenuContent>
+                      <ul className="grid gap-3 p-6 w-[300px]">
+                        {userMenuItems.map((item) => (
+                          <li key={item.title}>
+                            <NavigationMenuLink asChild>
+                              <Link
+                                href={item.href}
+                                className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground"
+                              >
+                                <div className="flex items-center gap-2">
+                                  <item.icon className="h-4 w-4" />
+                                  <div className="text-sm font-medium leading-none">{item.title}</div>
+                                </div>
+                                <p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
+                                  {item.description}
+                                </p>
+                              </Link>
+                            </NavigationMenuLink>
+                          </li>
+                        ))}
+                        <li>
+                          <LogoutLink className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground">
+                            <div className="flex items-center gap-2">
+                              <LogOut className="h-4 w-4" />
+                              <div className="text-sm font-medium leading-none">Logout</div>
+                            </div>
+                            <p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
+                              Sign out of your account
+                            </p>
+                          </LogoutLink>
+                        </li>
+                      </ul>
+                    </NavigationMenuContent>
+                  </NavigationMenuItem>
+                </NavigationMenuList>
+              </NavigationMenu>
+              
+              <div className="hidden md:flex items-center gap-2">
+                <LogoutLink>
+                  <Button variant="ghost" size="sm">
+                    <LogOut className="h-4 w-4 mr-2" />
+                    Logout
+                  </Button>
+                </LogoutLink>
+              </div>
+            </>
+          ) : (
+            <div className="hidden md:flex items-center gap-2">
+              <LoginLink>
+                <Button variant="ghost" size="sm">
+                  <LogIn className="h-4 w-4 mr-2" />
+                  Login
+                </Button>
               </LoginLink>
-            }
-            { user &&
-              <>
-                <Image
-                  className="rounded-full ml-auto"
-                  src={user.picture!}
-                  alt="User Avatar"
-                  width={35}
-                  height={35}
-                />
-                {user.email}
-              </>
-            }
-            { isAuthenticated &&
-              <LogoutLink
-                className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5">
-                Logout
-              </LogoutLink>
-            }
-          </div>
+              <RegisterLink>
+                <Button size="sm">Sign Up</Button>
+              </RegisterLink>
+            </div>
+          )}
+          
+          {/* Mobile Menu */}
+          <Sheet>
+            <SheetTrigger asChild className="md:hidden">
+              <Button variant="ghost" size="icon">
+                <Menu className="h-5 w-5" />
+                <span className="sr-only">Toggle menu</span>
+              </Button>
+            </SheetTrigger>
+            <SheetContent side="right" className="w-[300px]">
+              <div className="flex flex-col space-y-4 mt-8">
+                <Link href="/" className="flex items-center space-x-2">
+                  <Home className="h-4 w-4" />
+                  <span>Home</span>
+                </Link>
+                
+                {navigationItems.map((item) => (
+                  <Link
+                    key={item.title}
+                    href={item.href}
+                    className="flex items-center space-x-2"
+                  >
+                    <item.icon className="h-4 w-4" />
+                    <span>{item.title}</span>
+                  </Link>
+                ))}
+                
+                {isAuthenticated ? (
+                  <>
+                    <div className="border-t pt-4">
+                      <div className="flex items-center space-x-2 mb-4">
+                        <Image
+                          className="rounded-full"
+                          src={user?.picture || "/default-avatar.png"}
+                          alt="User Avatar"
+                          width={32}
+                          height={32}
+                        />
+                        <span className="font-medium">{user?.given_name || user?.email}</span>
+                      </div>
+                      
+                      {userMenuItems.map((item) => (
+                        <Link
+                          key={item.title}
+                          href={item.href}
+                          className="flex items-center space-x-2 py-2"
+                        >
+                          <item.icon className="h-4 w-4" />
+                          <span>{item.title}</span>
+                        </Link>
+                      ))}
+                      
+                      <LogoutLink className="flex items-center space-x-2 py-2 text-red-600">
+                        <LogOut className="h-4 w-4" />
+                        <span>Logout</span>
+                      </LogoutLink>
+                    </div>
+                  </>
+                ) : (
+                  <div className="border-t pt-4 space-y-2">
+                    <LoginLink>
+                      <Button variant="ghost" className="w-full justify-start">
+                        <LogIn className="h-4 w-4 mr-2" />
+                        Login
+                      </Button>
+                    </LoginLink>
+                    <RegisterLink>
+                      <Button className="w-full">Sign Up</Button>
+                    </RegisterLink>
+                  </div>
+                )}
+              </div>
+            </SheetContent>
+          </Sheet>
         </div>
       </div>
     </header>

+ 140 - 0
components/ui/sheet.tsx

@@ -0,0 +1,140 @@
+"use client"
+
+import * as React from "react"
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+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">
+        <X 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,
+}

+ 1 - 0
package-lock.json

@@ -14,6 +14,7 @@
         "@radix-ui/react-alert-dialog": "^1.1.14",
         "@radix-ui/react-avatar": "^1.1.10",
         "@radix-ui/react-checkbox": "^1.3.2",
+        "@radix-ui/react-dialog": "^1.1.14",
         "@radix-ui/react-dropdown-menu": "^2.1.15",
         "@radix-ui/react-label": "^2.1.7",
         "@radix-ui/react-navigation-menu": "^1.2.13",

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "@radix-ui/react-alert-dialog": "^1.1.14",
     "@radix-ui/react-avatar": "^1.1.10",
     "@radix-ui/react-checkbox": "^1.3.2",
+    "@radix-ui/react-dialog": "^1.1.14",
     "@radix-ui/react-dropdown-menu": "^2.1.15",
     "@radix-ui/react-label": "^2.1.7",
     "@radix-ui/react-navigation-menu": "^1.2.13",