code: tsx,nextjs,frontends (7 files)

This commit is contained in:
Richard Ward
2025-12-30 17:52:52 +00:00
parent 9713c5c144
commit 6657eb7e25
7 changed files with 82 additions and 135 deletions

View File

@@ -1,7 +1,7 @@
'use client'
import { AppBar, Slide, Toolbar, useScrollTrigger } from '@mui/material'
import { forwardRef, ReactNode } from 'react'
import { AppBar, Toolbar, Slide } from '@/fakemui'
import { forwardRef, ReactNode, useEffect, useState } from 'react'
import {
NavigationContent,
@@ -13,6 +13,7 @@ import {
} from './NavigationMenuItems'
import { NavigationMobileToggle } from './NavigationResponsive'
import { NavigationBrand, NavigationSeparator, NavigationSpacer } from './NavigationStyling'
import styles from './Navigation.module.scss'
interface NavigationProps {
children: ReactNode
@@ -22,6 +23,29 @@ interface NavigationProps {
hideOnScroll?: boolean
}
/**
* Custom hook to detect scroll direction for hide-on-scroll behavior
*/
const useScrollTrigger = (): boolean => {
const [trigger, setTrigger] = useState(false)
useEffect(() => {
let lastScrollY = window.scrollY
const threshold = 100
const handleScroll = () => {
const currentScrollY = window.scrollY
setTrigger(currentScrollY > threshold && currentScrollY > lastScrollY)
lastScrollY = currentScrollY
}
window.addEventListener('scroll', handleScroll, { passive: true })
return () => window.removeEventListener('scroll', handleScroll)
}, [])
return trigger
}
const Navigation = forwardRef<HTMLElement, NavigationProps>(
(
{
@@ -41,21 +65,16 @@ const Navigation = forwardRef<HTMLElement, NavigationProps>(
ref={ref}
position={position}
color={color}
elevation={elevation}
sx={{
bgcolor: 'background.paper',
borderBottom: 1,
borderColor: 'divider',
}}
className={styles.appBar}
{...props}
>
<Toolbar>{children}</Toolbar>
<Toolbar className={styles.toolbar}>{children}</Toolbar>
</AppBar>
)
if (hideOnScroll) {
return (
<Slide appear={false} direction="down" in={!trigger}>
<Slide direction="down" in={!trigger}>
{appBar}
</Slide>
)

View File

@@ -1,6 +1,7 @@
import { ExpandMore as ExpandMoreIcon } from '@/fakemui/icons'
import { Box, Button, ListItemIcon, ListItemText, Menu, MenuItem } from '@mui/material'
import { Box, Button, Menu, MenuItem, ListItemIcon, ListItemText } from '@/fakemui'
import { ElementType, forwardRef, type MouseEvent, ReactNode } from 'react'
import styles from './Navigation.module.scss'
interface NavigationMenuProps {
children: ReactNode
@@ -12,11 +13,7 @@ const NavigationMenu = forwardRef<HTMLDivElement, NavigationMenuProps>(
<Box
ref={ref}
component="nav"
sx={{
display: 'flex',
alignItems: 'center',
gap: 0.5,
}}
className={styles.navMenu}
{...props}
>
{children}
@@ -36,14 +33,7 @@ const NavigationList = forwardRef<HTMLUListElement, NavigationListProps>(
<Box
ref={ref}
component="ul"
sx={{
display: 'flex',
alignItems: 'center',
gap: 0.5,
listStyle: 'none',
m: 0,
p: 0,
}}
className={styles.navList}
{...props}
>
{children}
@@ -60,7 +50,7 @@ interface NavigationItemProps {
const NavigationItem = forwardRef<HTMLLIElement, NavigationItemProps>(
({ children, ...props }, ref) => {
return (
<Box component="li" ref={ref} sx={{ position: 'relative' }} {...props}>
<Box component="li" ref={ref} className={styles.navItem} {...props}>
{children}
</Box>
)
@@ -80,16 +70,9 @@ const NavigationTrigger = forwardRef<HTMLButtonElement, NavigationTriggerProps>(
<Button
ref={ref}
onClick={onClick}
color="inherit"
endIcon={hasDropdown ? <ExpandMoreIcon fontSize="small" /> : undefined}
sx={{
textTransform: 'none',
fontWeight: 500,
color: 'text.primary',
'&:hover': {
bgcolor: 'action.hover',
},
}}
variant="ghost"
endIcon={hasDropdown ? <ExpandMoreIcon /> : undefined}
className={styles.navTrigger}
{...props}
>
{children}
@@ -114,21 +97,7 @@ const NavigationContent = forwardRef<HTMLDivElement, NavigationContentProps>(
anchorEl={anchorEl}
open={open}
onClose={onClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
PaperProps={{
elevation: 2,
sx: {
mt: 1,
minWidth: 200,
},
}}
className={styles.navContent}
{...props}
>
{children}
@@ -149,7 +118,7 @@ interface NavigationLinkProps {
component?: ElementType
}
const NavigationLink = forwardRef<HTMLElement, NavigationLinkProps>(
const NavigationLink = forwardRef<HTMLButtonElement, NavigationLinkProps>(
(
{
children,
@@ -164,35 +133,25 @@ const NavigationLink = forwardRef<HTMLElement, NavigationLinkProps>(
},
ref
) => {
if (asChild || component) {
return (
<MenuItem
ref={ref}
component={component || 'a'}
href={href}
onClick={onClick}
disabled={disabled}
selected={active}
{...props}
>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText>{children}</ListItemText>
</MenuItem>
)
const handleClick = () => {
if (href && !onClick) {
window.location.href = href
} else if (onClick) {
onClick()
}
}
return (
<MenuItem
onClick={onClick}
ref={ref}
onClick={handleClick}
disabled={disabled}
selected={active}
sx={{
borderRadius: 1,
}}
className={`${styles.navLink} ${active ? styles.active : ''}`}
{...props}
>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText>{children}</ListItemText>
{icon && <span className={styles.linkIcon}>{icon}</span>}
<span>{children}</span>
</MenuItem>
)
}

View File

@@ -1,6 +1,7 @@
import { Menu as MenuIcon } from '@/fakemui/icons'
import { IconButton } from '@mui/material'
import { IconButton } from '@/fakemui'
import { forwardRef } from 'react'
import styles from './Navigation.module.scss'
interface NavigationMobileToggleProps {
onClick: () => void
@@ -12,12 +13,7 @@ const NavigationMobileToggle = forwardRef<HTMLButtonElement, NavigationMobileTog
<IconButton
ref={ref}
onClick={onClick}
edge="start"
color="inherit"
sx={{
display: { sm: 'none' },
mr: 2,
}}
className={styles.mobileToggle}
{...props}
>
<MenuIcon />

View File

@@ -1,5 +1,6 @@
import { Box, Divider } from '@mui/material'
import { Box, Divider } from '@/fakemui'
import { forwardRef, ReactNode } from 'react'
import styles from './Navigation.module.scss'
interface NavigationBrandProps {
children: ReactNode
@@ -13,12 +14,7 @@ const NavigationBrand = forwardRef<HTMLDivElement, NavigationBrandProps>(
<Box
ref={ref}
onClick={onClick}
sx={{
display: 'flex',
alignItems: 'center',
cursor: onClick || href ? 'pointer' : 'default',
mr: 2,
}}
className={`${styles.brand} ${onClick || href ? styles.clickable : ''}`}
{...props}
>
{children}
@@ -29,12 +25,12 @@ const NavigationBrand = forwardRef<HTMLDivElement, NavigationBrandProps>(
NavigationBrand.displayName = 'NavigationBrand'
const NavigationSeparator = forwardRef<HTMLHRElement, Record<string, never>>((props, ref) => {
return <Divider ref={ref} orientation="vertical" flexItem sx={{ mx: 1 }} {...props} />
return <Divider ref={ref} vertical className={styles.separator} {...props} />
})
NavigationSeparator.displayName = 'NavigationSeparator'
const NavigationSpacer = forwardRef<HTMLDivElement, Record<string, never>>((props, ref) => {
return <Box ref={ref} sx={{ flexGrow: 1 }} {...props} />
return <Box ref={ref} className={styles.spacer} {...props} />
})
NavigationSpacer.displayName = 'NavigationSpacer'

View File

@@ -1,12 +1,13 @@
'use client'
import { Menu as MenuIcon } from '@/fakemui/icons'
import { Box, Drawer, IconButton, useMediaQuery, useTheme } from '@mui/material'
import { Box, Drawer, IconButton, useMediaQuery } from '@/fakemui'
import { forwardRef, ReactNode } from 'react'
import { MenuItemList, type MenuItemListProps, type SidebarItem } from './MenuItemList'
import { SidebarHeader, type SidebarHeaderProps } from './Sidebar/Header'
import { SidebarSection, SidebarSeparator } from './Sidebar/NavSections'
import styles from './Navigation.module.scss'
interface SidebarProps {
children?: ReactNode
@@ -30,8 +31,8 @@ const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
},
ref
) => {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
// Use media query to determine mobile vs desktop
const isMobile = useMediaQuery('(max-width: 959px)')
const actualVariant = isMobile ? 'temporary' : variant
return (
@@ -41,20 +42,16 @@ const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
anchor={anchor}
open={open}
onClose={onClose}
sx={{
width: open ? width : 0,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: width,
boxSizing: 'border-box',
bgcolor: 'background.paper',
borderRight: 1,
borderColor: 'divider',
},
}}
className={styles.sidebar}
style={{
'--sidebar-width': `${width}px`,
width: open ? width : 0
} as React.CSSProperties}
{...props}
>
{children}
<div className={styles.sidebarPaper} style={{ width }}>
{children}
</div>
</Drawer>
)
}
@@ -69,11 +66,7 @@ const SidebarContent = forwardRef<HTMLDivElement, SidebarContentProps>(
({ children, ...props }, ref) => (
<Box
ref={ref}
sx={{
flex: 1,
overflow: 'auto',
py: 1,
}}
className={styles.sidebarContent}
{...props}
>
{children}
@@ -90,11 +83,7 @@ const SidebarFooter = forwardRef<HTMLDivElement, SidebarFooterProps>(
({ children, ...props }, ref) => (
<Box
ref={ref}
sx={{
p: 2,
borderTop: 1,
borderColor: 'divider',
}}
className={styles.sidebarFooter}
{...props}
>
{children}
@@ -109,7 +98,7 @@ interface SidebarToggleProps {
const SidebarToggle = forwardRef<HTMLButtonElement, SidebarToggleProps>(
({ onClick, ...props }, ref) => (
<IconButton ref={ref} onClick={onClick} edge="start" {...props}>
<IconButton ref={ref} onClick={onClick} {...props}>
<MenuIcon />
</IconButton>
)

View File

@@ -1,6 +1,7 @@
import { ChevronLeft as ChevronLeftIcon } from '@/fakemui/icons'
import { Box, IconButton } from '@mui/material'
import { Box, IconButton } from '@/fakemui'
import { forwardRef, ReactNode } from 'react'
import styles from '../Navigation.module.scss'
interface SidebarHeaderProps {
children?: ReactNode
@@ -13,18 +14,12 @@ const SidebarHeader = forwardRef<HTMLDivElement, SidebarHeaderProps>(
return (
<Box
ref={ref}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
p: 2,
minHeight: 64,
}}
className={styles.sidebarHeader}
{...props}
>
{children}
{showCloseButton && onClose && (
<IconButton onClick={onClose} size="small">
<IconButton onClick={onClose} sm>
<ChevronLeftIcon />
</IconButton>
)}

View File

@@ -1,5 +1,6 @@
import { Box, Divider, Typography } from '@mui/material'
import { Box, Divider, Typography } from '@/fakemui'
import { forwardRef, ReactNode } from 'react'
import styles from '../Navigation.module.scss'
interface SidebarSectionProps {
title?: string
@@ -9,19 +10,11 @@ interface SidebarSectionProps {
const SidebarSection = forwardRef<HTMLDivElement, SidebarSectionProps>(
({ title, children, ...props }, ref) => {
return (
<Box ref={ref} sx={{ py: 1 }} {...props}>
<Box ref={ref} className={styles.sidebarSection} {...props}>
{title && (
<Typography
variant="caption"
sx={{
px: 2,
py: 1,
display: 'block',
color: 'text.secondary',
fontWeight: 600,
textTransform: 'uppercase',
letterSpacing: '0.05em',
}}
className={styles.sectionTitle}
>
{title}
</Typography>
@@ -34,7 +27,7 @@ const SidebarSection = forwardRef<HTMLDivElement, SidebarSectionProps>(
SidebarSection.displayName = 'SidebarSection'
const SidebarSeparator = forwardRef<HTMLHRElement, Record<string, never>>((props, ref) => {
return <Divider ref={ref} sx={{ my: 1 }} {...props} />
return <Divider ref={ref} className={styles.sidebarSeparator} {...props} />
})
SidebarSeparator.displayName = 'SidebarSeparator'