🎉 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