From 55b0838f3af37112eb28b7f78835e61a2ea6432c Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Sun, 13 Oct 2024 22:24:02 -0400 Subject: [PATCH] config layout --- package.json | 2 + src/app/configuration/components/Header.tsx | 80 +++++++ .../configuration/components/TopbarNav.tsx | 58 +++++ src/app/configuration/layout.tsx | 73 +++---- src/app/configuration/resources/page.tsx | 9 +- src/app/configuration/sites/page.tsx | 10 +- src/components/ui/avatar.tsx | 50 +++++ src/components/ui/badge.tsx | 36 ++++ src/components/ui/dropdown-menu.tsx | 200 ++++++++++++++++++ 9 files changed, 471 insertions(+), 47 deletions(-) create mode 100644 src/app/configuration/components/Header.tsx create mode 100644 src/app/configuration/components/TopbarNav.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/dropdown-menu.tsx diff --git a/package.json b/package.json index 52c0f62a..451657ef 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,10 @@ "@node-rs/argon2": "1.8.3", "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", + "@radix-ui/react-avatar": "1.1.1", "@radix-ui/react-checkbox": "1.1.2", "@radix-ui/react-dialog": "1.1.2", + "@radix-ui/react-dropdown-menu": "2.1.2", "@radix-ui/react-icons": "1.3.0", "@radix-ui/react-label": "2.1.0", "@radix-ui/react-popover": "1.1.2", diff --git a/src/app/configuration/components/Header.tsx b/src/app/configuration/components/Header.tsx new file mode 100644 index 00000000..5075a5b7 --- /dev/null +++ b/src/app/configuration/components/Header.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { Avatar, AvatarFallback } from "@app/components/ui/avatar"; +import { Badge } from "@app/components/ui/badge"; +import { Button } from "@app/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@app/components/ui/dropdown-menu"; + +type HeaderProps = { + name?: string; + email: string; + orgName: string; +}; + +export default function Header({ email, orgName, name }: HeaderProps) { + function getInitials() { + if (name) { + const [firstName, lastName] = name.split(" "); + return `${firstName[0]}${lastName[0]}`; + } + return email.substring(0, 2).toUpperCase(); + } + + return ( + <> +
+ {orgName} + +
+ {name || email} + + + + + + +
+ {name && ( +

+ {name} +

+ )} +

+ {email} +

+
+
+ + + Profile + Log out + +
+
+
+
+ + ); +} diff --git a/src/app/configuration/components/TopbarNav.tsx b/src/app/configuration/components/TopbarNav.tsx new file mode 100644 index 00000000..2ba25df5 --- /dev/null +++ b/src/app/configuration/components/TopbarNav.tsx @@ -0,0 +1,58 @@ +"use client"; + +import React from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; + +interface TopbarNavProps extends React.HTMLAttributes { + items: { + href: string; + title: string; + icon: React.ReactNode; + }[]; + disabled?: boolean; +} + +export function TopbarNav({ + className, + items, + disabled = false, + ...props +}: TopbarNavProps) { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/src/app/configuration/layout.tsx b/src/app/configuration/layout.tsx index e8d7c4d8..a57af186 100644 --- a/src/app/configuration/layout.tsx +++ b/src/app/configuration/layout.tsx @@ -1,64 +1,49 @@ -import { Metadata } from "next" -import Image from "next/image" - -import { Separator } from "@/components/ui/separator" -import { SidebarNav } from "@/components/sidebar-nav" +import { Metadata } from "next"; +import { TopbarNav } from "./components/TopbarNav"; +import { LayoutGrid, Tent } from "lucide-react"; +import Header from "./components/Header"; export const metadata: Metadata = { - title: "Forms", - description: "Advanced form example using react-hook-form and Zod.", -} + title: "Configuration", + description: "", +}; -const sidebarNavItems = [ +const topNavItems = [ { title: "Sites", href: "/configuration/sites", + icon: , }, { title: "Resources", href: "/configuration/resources", + icon: , }, -] +]; -interface SettingsLayoutProps { - children: React.ReactNode, - params: { siteId: string } +interface ConfigurationLaytoutProps { + children: React.ReactNode; + params: { siteId: string }; } -export default function SettingsLayout({ children, params }: SettingsLayoutProps) { +export default async function ConfigurationLaytout({ + children, + params, +}: ConfigurationLaytoutProps) { return ( <> -
- Forms - Forms -
-
-
-

Settings

-

- { params.siteId == "create" ? "Create site..." : "Manage settings on site " + params.siteId }. -

-
- -
- -
{children}
+
+
+
+
+ +
{children}
- ) + ); } diff --git a/src/app/configuration/resources/page.tsx b/src/app/configuration/resources/page.tsx index f720649d..4b89334e 100644 --- a/src/app/configuration/resources/page.tsx +++ b/src/app/configuration/resources/page.tsx @@ -1,7 +1,14 @@ export default async function Page() { return ( <> -

This is where the table goes...

+
+

+ Manage Resources +

+

+ Create secure proxies to your private resources. +

+
); } diff --git a/src/app/configuration/sites/page.tsx b/src/app/configuration/sites/page.tsx index ab6e57ba..a01c9866 100644 --- a/src/app/configuration/sites/page.tsx +++ b/src/app/configuration/sites/page.tsx @@ -3,8 +3,14 @@ import Link from "next/link"; export default async function Page() { return ( <> -

This is where the table goes...

- Open up the site 123 +
+

+ Manage Sites +

+

+ Manage your existing sites here or create a new one. +

+
); } diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 00000000..51e507ba --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 00000000..f000e3ef --- /dev/null +++ b/src/components/ui/badge.tsx @@ -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-full 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 hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..f69a0d64 --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,200 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +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, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}