From 59efb7ea1ad22d48b0ed6ad74eb20cdff311e349 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 27 Dec 2025 17:39:45 +0000 Subject: [PATCH] refactor: split navigation components --- .../ui/organisms/navigation/Navigation.tsx | 325 +----------------- .../navigation/NavigationMenuItems.tsx | 203 +++++++++++ .../navigation/NavigationResponsive.tsx | 30 ++ .../navigation/NavigationStyling.tsx | 45 +++ .../organisms/navigation/navigationConfig.ts | 12 + .../organisms/navigation/navigationHelpers.ts | 23 ++ 6 files changed, 327 insertions(+), 311 deletions(-) create mode 100644 frontends/nextjs/src/components/ui/organisms/navigation/NavigationMenuItems.tsx create mode 100644 frontends/nextjs/src/components/ui/organisms/navigation/NavigationResponsive.tsx create mode 100644 frontends/nextjs/src/components/ui/organisms/navigation/NavigationStyling.tsx create mode 100644 frontends/nextjs/src/components/ui/organisms/navigation/navigationConfig.ts create mode 100644 frontends/nextjs/src/components/ui/organisms/navigation/navigationHelpers.ts diff --git a/frontends/nextjs/src/components/ui/organisms/navigation/Navigation.tsx b/frontends/nextjs/src/components/ui/organisms/navigation/Navigation.tsx index 4cc7db4a5..26db2774f 100644 --- a/frontends/nextjs/src/components/ui/organisms/navigation/Navigation.tsx +++ b/frontends/nextjs/src/components/ui/organisms/navigation/Navigation.tsx @@ -1,36 +1,20 @@ -// TODO: Split this file (370 LOC) into smaller organisms (<150 LOC each) 'use client' -import { forwardRef, ReactNode, useState, ElementType } from 'react' +import { forwardRef, ReactNode } from 'react' +import { AppBar, Toolbar, useScrollTrigger, Slide } from '@mui/material' +import { NavigationMobileToggle } from './NavigationResponsive' import { - AppBar, - Toolbar, - Box, - Typography, - IconButton, - Menu, - MenuItem, - ListItemIcon, - ListItemText, - Divider, - Button, - useScrollTrigger, - Slide, -} from '@mui/material' -import MenuIcon from '@mui/icons-material/Menu' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' + NavigationMenu, + NavigationList, + NavigationItem, + NavigationTrigger, + NavigationContent, + NavigationLink, +} from './NavigationMenuItems' +import { NavigationBrand, NavigationSeparator, NavigationSpacer } from './NavigationStyling' +import { NavigationItemType } from './navigationConfig' +import { useNavigationDropdown } from './navigationHelpers' -// Types -interface NavigationItem { - label: string - href?: string - onClick?: () => void - icon?: ReactNode - children?: NavigationItem[] - disabled?: boolean -} - -// Navigation Root (AppBar) interface NavigationProps { children: ReactNode position?: 'fixed' | 'absolute' | 'sticky' | 'static' | 'relative' @@ -73,287 +57,6 @@ const Navigation = forwardRef( ) Navigation.displayName = 'Navigation' -// NavigationMenu - Container for nav items -interface NavigationMenuProps { - children: ReactNode -} - -const NavigationMenu = forwardRef( - ({ children, ...props }, ref) => { - return ( - - {children} - - ) - } -) -NavigationMenu.displayName = 'NavigationMenu' - -// NavigationList - List of items -interface NavigationListProps { - children: ReactNode -} - -const NavigationList = forwardRef( - ({ children, ...props }, ref) => { - return ( - - {children} - - ) - } -) -NavigationList.displayName = 'NavigationList' - -// NavigationItem - Single nav item (may have dropdown) -interface NavigationItemProps { - children: ReactNode -} - -const NavigationItem = forwardRef( - ({ children, ...props }, ref) => { - return ( - - {children} - - ) - } -) -NavigationItem.displayName = 'NavigationItem' - -// NavigationTrigger - Button that opens dropdown -interface NavigationTriggerProps { - children: ReactNode - onClick?: (event: React.MouseEvent) => void - hasDropdown?: boolean -} - -const NavigationTrigger = forwardRef( - ({ children, onClick, hasDropdown = false, ...props }, ref) => { - return ( - - ) - } -) -NavigationTrigger.displayName = 'NavigationTrigger' - -// NavigationContent - Dropdown content -interface NavigationContentProps { - anchorEl: HTMLElement | null - open: boolean - onClose: () => void - children: ReactNode -} - -const NavigationContent = forwardRef( - ({ anchorEl, open, onClose, children, ...props }, ref) => { - return ( - - {children} - - ) - } -) -NavigationContent.displayName = 'NavigationContent' - -// NavigationLink - Link within navigation -interface NavigationLinkProps { - children: ReactNode - href?: string - onClick?: () => void - active?: boolean - disabled?: boolean - icon?: ReactNode - asChild?: boolean - component?: ElementType -} - -const NavigationLink = forwardRef( - ({ children, href, onClick, active = false, disabled = false, icon, asChild, component, ...props }, ref) => { - if (asChild || component) { - return ( - - {icon && {icon}} - {children} - - ) - } - - return ( - - {icon && {icon}} - {children} - - ) - } -) -NavigationLink.displayName = 'NavigationLink' - -// NavigationBrand - Logo/brand area -interface NavigationBrandProps { - children: ReactNode - href?: string - onClick?: () => void -} - -const NavigationBrand = forwardRef( - ({ children, href, onClick, ...props }, ref) => { - return ( - - {children} - - ) - } -) -NavigationBrand.displayName = 'NavigationBrand' - -// NavigationSeparator -const NavigationSeparator = forwardRef>( - (props, ref) => { - return - } -) -NavigationSeparator.displayName = 'NavigationSeparator' - -// NavigationSpacer - Flex spacer -const NavigationSpacer = forwardRef>( - (props, ref) => { - return - } -) -NavigationSpacer.displayName = 'NavigationSpacer' - -// NavigationMobileToggle - Hamburger menu for mobile -interface NavigationMobileToggleProps { - onClick: () => void -} - -const NavigationMobileToggle = forwardRef( - ({ onClick, ...props }, ref) => { - return ( - - - - ) - } -) -NavigationMobileToggle.displayName = 'NavigationMobileToggle' - -// Helper hook for dropdown navigation -function useNavigationDropdown() { - const [anchorEl, setAnchorEl] = useState(null) - const open = Boolean(anchorEl) - - const handleOpen = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget) - } - - const handleClose = () => { - setAnchorEl(null) - } - - return { - anchorEl, - open, - handleOpen, - handleClose, - } -} - export { Navigation, NavigationMenu, @@ -368,4 +71,4 @@ export { NavigationMobileToggle, useNavigationDropdown, } -export type { NavigationItem as NavigationItemType } +export type { NavigationItemType } diff --git a/frontends/nextjs/src/components/ui/organisms/navigation/NavigationMenuItems.tsx b/frontends/nextjs/src/components/ui/organisms/navigation/NavigationMenuItems.tsx new file mode 100644 index 000000000..ef51ed399 --- /dev/null +++ b/frontends/nextjs/src/components/ui/organisms/navigation/NavigationMenuItems.tsx @@ -0,0 +1,203 @@ +import { forwardRef, ReactNode, ElementType, type MouseEvent } from 'react' +import { + Box, + Button, + ListItemIcon, + ListItemText, + Menu, + MenuItem, +} from '@mui/material' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' + +interface NavigationMenuProps { + children: ReactNode +} + +const NavigationMenu = forwardRef( + ({ children, ...props }, ref) => { + return ( + + {children} + + ) + } +) +NavigationMenu.displayName = 'NavigationMenu' + +interface NavigationListProps { + children: ReactNode +} + +const NavigationList = forwardRef( + ({ children, ...props }, ref) => { + return ( + + {children} + + ) + } +) +NavigationList.displayName = 'NavigationList' + +interface NavigationItemProps { + children: ReactNode +} + +const NavigationItem = forwardRef( + ({ children, ...props }, ref) => { + return ( + + {children} + + ) + } +) +NavigationItem.displayName = 'NavigationItem' + +interface NavigationTriggerProps { + children: ReactNode + onClick?: (event: MouseEvent) => void + hasDropdown?: boolean +} + +const NavigationTrigger = forwardRef( + ({ children, onClick, hasDropdown = false, ...props }, ref) => { + return ( + + ) + } +) +NavigationTrigger.displayName = 'NavigationTrigger' + +interface NavigationContentProps { + anchorEl: HTMLElement | null + open: boolean + onClose: () => void + children: ReactNode +} + +const NavigationContent = forwardRef( + ({ anchorEl, open, onClose, children, ...props }, ref) => { + return ( + + {children} + + ) + } +) +NavigationContent.displayName = 'NavigationContent' + +interface NavigationLinkProps { + children: ReactNode + href?: string + onClick?: () => void + active?: boolean + disabled?: boolean + icon?: ReactNode + asChild?: boolean + component?: ElementType +} + +const NavigationLink = forwardRef( + ({ children, href, onClick, active = false, disabled = false, icon, asChild, component, ...props }, ref) => { + if (asChild || component) { + return ( + + {icon && {icon}} + {children} + + ) + } + + return ( + + {icon && {icon}} + {children} + + ) + } +) +NavigationLink.displayName = 'NavigationLink' + +export { + NavigationMenu, + NavigationList, + NavigationItem, + NavigationTrigger, + NavigationContent, + NavigationLink, +} diff --git a/frontends/nextjs/src/components/ui/organisms/navigation/NavigationResponsive.tsx b/frontends/nextjs/src/components/ui/organisms/navigation/NavigationResponsive.tsx new file mode 100644 index 000000000..6cedc4ecd --- /dev/null +++ b/frontends/nextjs/src/components/ui/organisms/navigation/NavigationResponsive.tsx @@ -0,0 +1,30 @@ +import { forwardRef } from 'react' +import { IconButton } from '@mui/material' +import MenuIcon from '@mui/icons-material/Menu' + +interface NavigationMobileToggleProps { + onClick: () => void +} + +const NavigationMobileToggle = forwardRef( + ({ onClick, ...props }, ref) => { + return ( + + + + ) + } +) +NavigationMobileToggle.displayName = 'NavigationMobileToggle' + +export { NavigationMobileToggle } diff --git a/frontends/nextjs/src/components/ui/organisms/navigation/NavigationStyling.tsx b/frontends/nextjs/src/components/ui/organisms/navigation/NavigationStyling.tsx new file mode 100644 index 000000000..c3a854269 --- /dev/null +++ b/frontends/nextjs/src/components/ui/organisms/navigation/NavigationStyling.tsx @@ -0,0 +1,45 @@ +import { forwardRef, ReactNode } from 'react' +import { Box, Divider } from '@mui/material' + +interface NavigationBrandProps { + children: ReactNode + href?: string + onClick?: () => void +} + +const NavigationBrand = forwardRef( + ({ children, href, onClick, ...props }, ref) => { + return ( + + {children} + + ) + } +) +NavigationBrand.displayName = 'NavigationBrand' + +const NavigationSeparator = forwardRef>( + (props, ref) => { + return + } +) +NavigationSeparator.displayName = 'NavigationSeparator' + +const NavigationSpacer = forwardRef>( + (props, ref) => { + return + } +) +NavigationSpacer.displayName = 'NavigationSpacer' + +export { NavigationBrand, NavigationSeparator, NavigationSpacer } diff --git a/frontends/nextjs/src/components/ui/organisms/navigation/navigationConfig.ts b/frontends/nextjs/src/components/ui/organisms/navigation/navigationConfig.ts new file mode 100644 index 000000000..02bd67edc --- /dev/null +++ b/frontends/nextjs/src/components/ui/organisms/navigation/navigationConfig.ts @@ -0,0 +1,12 @@ +import { ReactNode } from 'react' + +interface NavigationItem { + label: string + href?: string + onClick?: () => void + icon?: ReactNode + children?: NavigationItem[] + disabled?: boolean +} + +export type NavigationItemType = NavigationItem diff --git a/frontends/nextjs/src/components/ui/organisms/navigation/navigationHelpers.ts b/frontends/nextjs/src/components/ui/organisms/navigation/navigationHelpers.ts new file mode 100644 index 000000000..5c7746cde --- /dev/null +++ b/frontends/nextjs/src/components/ui/organisms/navigation/navigationHelpers.ts @@ -0,0 +1,23 @@ +import { useState, type MouseEvent } from 'react' + +function useNavigationDropdown() { + const [anchorEl, setAnchorEl] = useState(null) + const open = Boolean(anchorEl) + + const handleOpen = (event: MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + return { + anchorEl, + open, + handleOpen, + handleClose, + } +} + +export { useNavigationDropdown }