🎉 Initial Commit for v2!
This commit is contained in:
		
							
								
								
									
										138
									
								
								components/AppShell.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								components/AppShell.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| "use client"; | ||||
|  | ||||
| import { | ||||
|   Slide, | ||||
|   Toolbar, | ||||
|   Typography, | ||||
|   AppBar as MuiAppBar, | ||||
|   AppBarProps as MuiAppBarProps, | ||||
|   useScrollTrigger, | ||||
|   IconButton, | ||||
|   styled, Box, useMediaQuery | ||||
| } from "@mui/material"; | ||||
| import { ReactElement, ReactNode, useEffect, useState } from "react"; | ||||
| import { SITE_NAME } from "@/app/consts"; | ||||
| import NavigationDrawer, { DRAWER_WIDTH, AppNavigationHeader, isMobileQuery } from "@/components/NavigationDrawer"; | ||||
| import MenuIcon from "@mui/icons-material/Menu"; | ||||
| import Image from "next/image"; | ||||
|  | ||||
| function HideOnScroll(props: { | ||||
|   window?: () => Window; | ||||
|   children: ReactElement; | ||||
| }) { | ||||
|   const { children, window } = props; | ||||
|   const trigger = useScrollTrigger({ | ||||
|     target: window ? window() : undefined | ||||
|   }); | ||||
|  | ||||
|   return ( | ||||
|     <Slide appear={false} direction="down" in={!trigger}> | ||||
|       {children} | ||||
|     </Slide> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| interface AppBarProps extends MuiAppBarProps { | ||||
|   open?: boolean; | ||||
| } | ||||
|  | ||||
| const ShellAppBar = styled(MuiAppBar, { | ||||
|   shouldForwardProp: (prop) => prop !== "open" | ||||
| })<AppBarProps>(({ theme, open }) => { | ||||
|   const isMobile = useMediaQuery(isMobileQuery); | ||||
|  | ||||
|   return ({ | ||||
|     transition: theme.transitions.create(["margin", "width"], { | ||||
|       easing: theme.transitions.easing.sharp, | ||||
|       duration: theme.transitions.duration.leavingScreen | ||||
|     }), | ||||
|     ...(!isMobile && open && { | ||||
|       width: `calc(100% - ${DRAWER_WIDTH}px)`, | ||||
|       transition: theme.transitions.create(["margin", "width"], { | ||||
|         easing: theme.transitions.easing.easeOut, | ||||
|         duration: theme.transitions.duration.enteringScreen | ||||
|       }), | ||||
|       marginRight: DRAWER_WIDTH | ||||
|     }) | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| const AppMain = styled("main", { shouldForwardProp: (prop) => prop !== "open" })<{ | ||||
|   open?: boolean; | ||||
| }>(({ theme, open }) => { | ||||
|   const isMobile = useMediaQuery(isMobileQuery); | ||||
|  | ||||
|   return ({ | ||||
|     flexGrow: 1, | ||||
|     padding: theme.spacing(3), | ||||
|     transition: theme.transitions.create("margin", { | ||||
|       easing: theme.transitions.easing.sharp, | ||||
|       duration: theme.transitions.duration.leavingScreen | ||||
|     }), | ||||
|     marginRight: -DRAWER_WIDTH, | ||||
|     ...(!isMobile && open && { | ||||
|       transition: theme.transitions.create("margin", { | ||||
|         easing: theme.transitions.easing.easeOut, | ||||
|         duration: theme.transitions.duration.enteringScreen | ||||
|       }), | ||||
|       marginRight: 0 | ||||
|     }), | ||||
|     position: "relative" | ||||
|   }); | ||||
| }); | ||||
|  | ||||
|  | ||||
| export default function AppShell({ children }: { | ||||
|   children: ReactNode, | ||||
| }) { | ||||
|   let documentWindow: Window; | ||||
|  | ||||
|   const isMobile = useMediaQuery(isMobileQuery); | ||||
|   const [open, setOpen] = useState(false); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     documentWindow = window; | ||||
|   }) | ||||
|  | ||||
|   return ( | ||||
|     <Box sx={{ display: "flex" }}> | ||||
|       <HideOnScroll window={() => documentWindow}> | ||||
|         <ShellAppBar open={open}> | ||||
|           <Toolbar> | ||||
|             <IconButton | ||||
|               size="large" | ||||
|               edge="start" | ||||
|               color="inherit" | ||||
|               aria-label="menu" | ||||
|               sx={{ mr: 2 }} | ||||
|             > | ||||
|               <Image src="smartsheep.svg" alt="Logo" width={32} height={32} /> | ||||
|             </IconButton> | ||||
|  | ||||
|             <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}> | ||||
|               {SITE_NAME} | ||||
|             </Typography> | ||||
|  | ||||
|             <IconButton | ||||
|               size="large" | ||||
|               edge="start" | ||||
|               color="inherit" | ||||
|               aria-label="menu" | ||||
|               onClick={() => setOpen(true)} | ||||
|               sx={{ mr: 1, display: !isMobile && open ? "none" : "block" }} | ||||
|             > | ||||
|               <MenuIcon /> | ||||
|             </IconButton> | ||||
|           </Toolbar> | ||||
|         </ShellAppBar> | ||||
|       </HideOnScroll> | ||||
|  | ||||
|       <AppMain open={open}> | ||||
|         <AppNavigationHeader /> | ||||
|         {children} | ||||
|       </AppMain> | ||||
|  | ||||
|       <NavigationDrawer open={open} onClose={() => setOpen(false)} /> | ||||
|     </Box> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										119
									
								
								components/NavigationDrawer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								components/NavigationDrawer.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| "use client"; | ||||
|  | ||||
| import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; | ||||
| import ChevronRightIcon from "@mui/icons-material/ChevronRight"; | ||||
| import InboxIcon from "@mui/icons-material/MoveToInbox"; | ||||
| import MailIcon from "@mui/icons-material/Mail"; | ||||
| import { | ||||
|   Box, | ||||
|   Divider, | ||||
|   Drawer, | ||||
|   IconButton, | ||||
|   List, | ||||
|   ListItem, | ||||
|   ListItemButton, | ||||
|   ListItemIcon, | ||||
|   ListItemText, | ||||
|   styled, | ||||
|   useMediaQuery | ||||
| } from "@mui/material"; | ||||
| import { theme } from "@/app/theme"; | ||||
|  | ||||
| export const DRAWER_WIDTH = 320; | ||||
|  | ||||
| export const AppNavigationHeader = styled("div")(({ theme }) => ({ | ||||
|   display: "flex", | ||||
|   alignItems: "center", | ||||
|   padding: theme.spacing(0, 1), | ||||
|   justifyContent: "flex-start", | ||||
|   ...theme.mixins.toolbar | ||||
| })); | ||||
|  | ||||
| export function AppNavigation({ showClose, onClose }: { | ||||
|   showClose?: boolean, | ||||
|   onClose: () => void | ||||
| }) { | ||||
|   return ( | ||||
|     <> | ||||
|       <AppNavigationHeader> | ||||
|         { | ||||
|           showClose && | ||||
|           <IconButton onClick={onClose}> | ||||
|             {theme.direction === "rtl" ? <ChevronLeftIcon /> : <ChevronRightIcon />} | ||||
|           </IconButton> | ||||
|         } | ||||
|       </AppNavigationHeader> | ||||
|       <Divider /> | ||||
|       <List> | ||||
|         {["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => ( | ||||
|           <ListItem key={text} disablePadding> | ||||
|             <ListItemButton> | ||||
|               <ListItemIcon> | ||||
|                 {index % 2 === 0 ? <InboxIcon /> : <MailIcon />} | ||||
|               </ListItemIcon> | ||||
|               <ListItemText primary={text} /> | ||||
|             </ListItemButton> | ||||
|           </ListItem> | ||||
|         ))} | ||||
|       </List> | ||||
|       <Divider /> | ||||
|       <List> | ||||
|         {["All mail", "Trash", "Spam"].map((text, index) => ( | ||||
|           <ListItem key={text} disablePadding> | ||||
|             <ListItemButton> | ||||
|               <ListItemIcon> | ||||
|                 {index % 2 === 0 ? <InboxIcon /> : <MailIcon />} | ||||
|               </ListItemIcon> | ||||
|               <ListItemText primary={text} /> | ||||
|             </ListItemButton> | ||||
|           </ListItem> | ||||
|         ))} | ||||
|       </List> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export const isMobileQuery = theme.breakpoints.down("md"); | ||||
|  | ||||
| export default function NavigationDrawer({ open, onClose }: { | ||||
|   open: boolean, | ||||
|   onClose: () => void, | ||||
| }) { | ||||
|   const isMobile = useMediaQuery(isMobileQuery); | ||||
|  | ||||
|   return isMobile ? ( | ||||
|     <> | ||||
|       <Box sx={{ flexShrink: 0, width: DRAWER_WIDTH }} /> | ||||
|       <Drawer | ||||
|         keepMounted | ||||
|         anchor="right" | ||||
|         variant="temporary" | ||||
|         open={open} | ||||
|         onClose={onClose} | ||||
|         sx={{ | ||||
|           "& .MuiDrawer-paper": { | ||||
|             boxSizing: "border-box", | ||||
|             width: DRAWER_WIDTH | ||||
|           } | ||||
|         }} | ||||
|       > | ||||
|         <AppNavigation onClose={onClose} /> | ||||
|       </Drawer> | ||||
|     </> | ||||
|   ) : ( | ||||
|     <Drawer | ||||
|       variant="persistent" | ||||
|       anchor="right" | ||||
|       open={open} | ||||
|       sx={{ | ||||
|         width: DRAWER_WIDTH, | ||||
|         flexShrink: 0, | ||||
|         "& .MuiDrawer-paper": { | ||||
|           width: DRAWER_WIDTH | ||||
|         } | ||||
|       }} | ||||
|     > | ||||
|       <AppNavigation showClose onClose={onClose} /> | ||||
|     </Drawer> | ||||
|   ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user