diff --git a/frontend/package.json b/frontend/package.json index 9bca7a5..dbe897e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,15 +10,20 @@ "lint": "next lint" }, "dependencies": { - "@hookform/resolvers": "^3.3.0", + "@hookform/resolvers": "^3.3.1", + "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-scroll-area": "^1.0.4", + "@radix-ui/react-select": "^1.2.2", + "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.4", "@radix-ui/react-tooltip": "^1.0.6", "@tabler/icons-react": "^2.32.0", "@tanstack/react-query": "^4.33.0", + "@tanstack/react-table": "^8.9.3", "@types/node": "20.4.8", "@types/react": "18.2.18", "@types/react-dom": "18.2.7", @@ -26,9 +31,11 @@ "axios": "^1.4.0", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", + "cmdk": "^0.2.0", "eslint": "8.46.0", "eslint-config-next": "13.4.13", "jotai": "^2.3.1", + "jotai-optics": "^0.3.1", "js-cookie": "^3.0.5", "next": "13.4.13", "next-themes": "^0.2.1", @@ -36,12 +43,15 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.45.4", + "react-hot-toast": "^2.4.1", "tailwind-merge": "^1.14.0", "tailwindcss": "3.3.3", "tailwindcss-animate": "^1.0.6", - "typescript": "5.1.6" + "typescript": "5.1.6", + "zod": "^3.22.2" }, "devDependencies": { - "@types/js-cookie": "^3.0.3" + "@types/js-cookie": "^3.0.3", + "optics-ts": "^2.4.1" } } diff --git a/frontend/src/components/sidebar/actions/sidebar-actions.tsx b/frontend/src/components/sidebar/actions/sidebar-actions.tsx deleted file mode 100644 index 8bffc71..0000000 --- a/frontend/src/components/sidebar/actions/sidebar-actions.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { ReactNode } from 'react'; -import { - IconHome, - IconSearch, - IconSettings, - IconFilePlus, -} from '@tabler/icons-react'; - -export type NavigationMenuType = { - label: string; - path: string; - icon: ReactNode; - isActive?: boolean; - onClick?: React.MouseEventHandler; -}; -export const navigationMenu: NavigationMenuType[] = [ - { - label: 'Home', - path: '', - icon: , - }, - { - label: 'Search', - path: '', - icon: , - }, - { - label: 'Settings', - path: '', - icon: , - }, - { - label: 'New Page', - path: '', - icon: , - }, -]; diff --git a/frontend/src/components/sidebar/navigation/navigation-items.tsx b/frontend/src/components/sidebar/navigation/navigation-items.tsx new file mode 100644 index 0000000..0bc028f --- /dev/null +++ b/frontend/src/components/sidebar/navigation/navigation-items.tsx @@ -0,0 +1,68 @@ +import React, { ReactNode } from 'react'; +import { + IconHome, + IconSearch, + IconSettings, + IconFilePlus, +} from '@tabler/icons-react'; +import NavigationLink from "@/components/sidebar/navigation/navigation-link"; +import ButtonWithIcon from "@/components/ui/button-with-icon"; + +export type NavigationMenuType = { + label: string; + path: string; + icon: ReactNode; + target?: string, + onClick?: React.MouseEventHandler; +}; + +export const navigationMenu: NavigationMenuType[] = [ + { + label: 'Home', + path: '', + icon: , + target: '/home', + }, + { + label: 'Search', + path: '', + icon: , + }, + { + label: 'Settings', + path: '', + icon: , + target: '/settings/account' + }, + { + label: 'New Page', + path: '', + icon: , + }, +]; + +export const renderMenuItem = (menu, index) => { + if (menu.target) { + return ( + + {menu.label} + + ); + } + + return ( + + {menu.label} + + ); +}; diff --git a/frontend/src/components/sidebar/navigation/navigation-link.tsx b/frontend/src/components/sidebar/navigation/navigation-link.tsx new file mode 100644 index 0000000..d04ef56 --- /dev/null +++ b/frontend/src/components/sidebar/navigation/navigation-link.tsx @@ -0,0 +1,25 @@ +import { ReactNode } from "react"; +import { buttonVariants } from "@/components/ui/button"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; + +interface NavigationLinkProps { + children: ReactNode, + href: string; + icon?: ReactNode; + variant?: "default" | "ghost" | "outline"; + className?: string; +} + +export default function NavigationLink({ children, href, icon, variant = "ghost", className }: NavigationLinkProps) { + return ( + + {icon && + {icon} + } + + {children} + + + ); +} diff --git a/frontend/src/components/sidebar/navigation/navigation.tsx b/frontend/src/components/sidebar/navigation/navigation.tsx new file mode 100644 index 0000000..1e5323d --- /dev/null +++ b/frontend/src/components/sidebar/navigation/navigation.tsx @@ -0,0 +1,40 @@ +import { SidebarSection } from "@/components/sidebar/sidebar-section"; +import { navigationMenu, renderMenuItem } from "@/components/sidebar/navigation/navigation-items"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import NavigationLink from "@/components/sidebar/navigation/navigation-link"; +import { IconFileText } from "@tabler/icons-react"; + +import React from "react"; + +export default function Navigation() { + return ( +
+ + +
+ ); +} + +function PrimaryNavigation() { + return ( + + {navigationMenu.map(renderMenuItem)} + + ); +} + +function SecondaryNavigationArea() { + return ( + +
+ } + > + Welcome page + +
+
+ ); +} diff --git a/frontend/src/components/sidebar/sidebar-toggle-button.tsx b/frontend/src/components/sidebar/sidebar-toggle-button.tsx index cb78168..a9cc510 100644 --- a/frontend/src/components/sidebar/sidebar-toggle-button.tsx +++ b/frontend/src/components/sidebar/sidebar-toggle-button.tsx @@ -32,10 +32,16 @@ export default function SidebarToggleButton({ return ( } variant={'ghost'} onClick={toggleSidebar} - > + /> + ); +} + +export function MobileSidebarToggle({ isSidebarOpen }) { + return ( + ); } diff --git a/frontend/src/components/sidebar/sidebar.tsx b/frontend/src/components/sidebar/sidebar.tsx index a8b0aa9..7e0562f 100644 --- a/frontend/src/components/sidebar/sidebar.tsx +++ b/frontend/src/components/sidebar/sidebar.tsx @@ -1,79 +1,56 @@ -import { useIsMobile } from '@/hooks/use-is-mobile'; -import { useAtom } from 'jotai'; +import { useIsMobile } from "@/hooks/use-is-mobile"; +import { useAtom } from "jotai"; import { desktopSidebarAtom, mobileSidebarAtom, -} from '@/components/sidebar/atoms/sidebar-atom'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { IconFileText } from '@tabler/icons-react'; -import { SidebarSection } from '@/components/sidebar/sidebar-section'; -import { - navigationMenu, - NavigationMenuType, -} from '@/components/sidebar/actions/sidebar-actions'; -import ButtonWithIcon from '@/components/ui/button-with-icon'; -import SidebarToggleButton from './sidebar-toggle-button'; +} from "@/components/sidebar/atoms/sidebar-atom"; +import { MobileSidebarToggle } from "./sidebar-toggle-button"; +import SettingsNav from "@/features/settings/nav/settings-nav"; +import { usePathname } from "next/navigation"; +import React, { useEffect } from "react"; +import Navigation from "@/components/sidebar/navigation/navigation"; export default function Sidebar() { const isMobile = useIsMobile(); + const pathname = usePathname(); + const [isSidebarOpen, setIsSidebarOpen] = useAtom(isMobile ? mobileSidebarAtom : desktopSidebarAtom); + const isSettings = pathname.startsWith("/settings"); - const [isSidebarOpen] = useAtom( - isMobile ? mobileSidebarAtom : desktopSidebarAtom - ); + const mobileClass = "fixed top-0 left-0 h-screen z-50 bg-background"; + const sidebarWidth = isSidebarOpen ? "w-[270px]" : "w-[0px]"; + + const closeSidebar = () => { + setIsSidebarOpen(false); + }; + + useEffect(() => { + if (isMobile) { + setIsSidebarOpen(false); + } + }, [pathname, isMobile, setIsSidebarOpen]); return ( - + + ); } diff --git a/frontend/src/components/sidebar/topbar.tsx b/frontend/src/components/sidebar/topbar.tsx index 563decf..f1b4561 100644 --- a/frontend/src/components/sidebar/topbar.tsx +++ b/frontend/src/components/sidebar/topbar.tsx @@ -7,12 +7,13 @@ export default function TopBar() { const isMobile = useIsMobile(); return ( -
-
+
+
-
+
{!isMobile && }
+
diff --git a/frontend/src/features/settings/nav/settings-nav-items.tsx b/frontend/src/features/settings/nav/settings-nav-items.tsx new file mode 100644 index 0000000..4423f86 --- /dev/null +++ b/frontend/src/features/settings/nav/settings-nav-items.tsx @@ -0,0 +1,50 @@ +'use client' + +import { ReactNode } from 'react'; +import { IconUserCircle, IconUser, IconUsers, + IconBuilding, IconSettingsCog } from '@tabler/icons-react'; + +export interface SettingsNavMenuSection { + heading: string; + icon: ReactNode; + items: SettingsNavMenuItem[]; +} + +export interface SettingsNavMenuItem { + label: string; + icon: ReactNode; + target?: string; +} + +export type SettingsNavItem = SettingsNavMenuSection[]; + +export const settingsNavItems: SettingsNavItem = [ + { + heading: 'Account', + icon: , + items: [ + { + label: 'My account', + icon: , + target: '/settings/account', + }, + ], + }, + { + heading: 'Workspace', + icon: , + items: [ + { + label: 'General', + icon: , + target: '/settings/workspace', + }, + { + label: 'Members', + icon: , + target: '/settings/workspace/members', + }, + ], + }, + +]; diff --git a/frontend/src/features/settings/nav/settings-nav.tsx b/frontend/src/features/settings/nav/settings-nav.tsx new file mode 100644 index 0000000..9e123e9 --- /dev/null +++ b/frontend/src/features/settings/nav/settings-nav.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { + SettingsNavItem, + SettingsNavMenuItem, SettingsNavMenuSection, + settingsNavItems +} from "@/features/settings/nav/settings-nav-items"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import React from "react"; +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; +import { ChevronLeftIcon } from "@radix-ui/react-icons"; + +interface SettingsNavProps { + menu: SettingsNavItem; +} + +function RenderNavItem({ label, icon, target }: SettingsNavMenuItem): React.ReactNode { + const pathname = usePathname(); + const isActive = pathname === target; + + return ( +
+ + {icon} + + {label} + + +
+ ); +} + +function SettingsNavItems({ menu }: SettingsNavProps): React.ReactNode { + return ( + <> +
+ + Back + +
+ +
+ {menu.map((section: SettingsNavMenuSection, index: number) => ( +
+

+ {section.icon} {section.heading} +

+ {section.items.map((item: SettingsNavMenuItem, itemIndex: number) => ( + + ))} +
+ ))} +
+ + ); +} + +export default function SettingsNav() { + return +}