Create Shell, Add Fonts, Create Navbar, Create Footer

This commit is contained in:
Matt DiMeglio 2025-05-23 18:22:56 -04:00
parent 21dbd59e93
commit 9602569ae8
13 changed files with 293 additions and 4 deletions

View file

@ -0,0 +1,24 @@
import React from 'react';
import styled from '@emotion/styled';
const FooterContainer = styled('div')`
padding-left: 10px;
position: absolute;
left: 0;
bottom: 0;
right: 0;
flex-shrink: 0;
height: 30px;
padding-top: 6px;
background: #585858;
color: white;
font-family: "WDXL Lubrifont TC";
`;
export const Footer = () => {
return (
<FooterContainer>
<p>{`© ${new Date().getFullYear()} ShiftSync`}</p>
</FooterContainer>
)
}

View file

@ -0,0 +1 @@
export { Footer } from './Footer';

View file

@ -0,0 +1,181 @@
import React, { useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import {
useLocation,
useNavigate,
useParams
} from 'react-router';
import { Stack } from '@mui/material';
const NavBox = styled(Stack)`
width: 100%;
`;
const LeftContainer = styled(Stack)`
display: flex;
align-items: center;
flex-direction: row;
flex-flow: row wrap;
`;
const RightContainer = styled(Stack)`
display: flex;
flex-direction: row;
flex-flow: row wrap;
`;
const NavGroup = styled(Stack)`
display: flex;
background: #ffffff
width: 100%;
height: 60px;
padding: 4px;
justify-content: space-between !important
`;
const Title = styled('p')`
&:hover {
cursor: pointer;
}
`;
const Tab = styled('div')`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
height: 100%;
padding: 6px 16px;
font-size: 20px;
font-weight: 800;
color: grey;
background: none;
border: none;
cursor: pointer;
font-family: "WDXL Lubrifont TC";
letter-spacing: .05em;
word-spacing: .2em;
`;
const SlidingIndicator = styled('div')`
position: absolute;
bottom: -6px;
height: 2px;
background-color: red;
transition: left 0.3s ease, width 0.3s ease;
`;
const UserButton = styled(Stack)`
width: 100%;
`;
const Border = styled('div')`
height: 2px;
width: 100%;
background-color: #A8A8A8;
`;
export const NavBar = ({ notifications, disableNav, settings }) => {
const { pathname } = useLocation();
const navigate = useNavigate();
const routeParams = useParams();
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
const tabRefs = useRef({});
const isAdmin = true; // to be changed to check
const navItems = [
{
name: 'Home',
id: 'home',
href: '/',
matches: ['', 'home']
},
{
name: 'About Us',
id: 'about',
href: '/about',
matches: ['about', 'aboutus']
},
{
name: 'Administrator Panel',
id: 'admin',
href: '/admin',
matches: ['admin'],
hidden: !isAdmin
}
];
let activeTab = navItems.find((tab) => {
return tab.matches.some((item) => {
return item === pathname?.split('/')[1];
});
});
const onShiftSyncClick = () => {
navigate('/');
}
const onNavButtonClick = (tab) => {
activeTab = tab;
navigate(tab.href);
};
const onUserIconClick = () => {
navigate('/settings');
};
useEffect(() => {
if (activeTab?.id && tabRefs.current[activeTab.id]) {
const el = tabRefs.current[activeTab.id];
const rect = el.getBoundingClientRect();
const containerRect = el.parentNode.getBoundingClientRect();
setIndicatorStyle({
left: rect.left - containerRect.left,
width: rect.width
});
}
}, [pathname, activeTab?.id]);
return (
<NavBox direction='column'>
<NavGroup direction='row'>
<LeftContainer>
{/* <Icon /> */}
<Title
style={{
color: 'darkgray',
fontWeight: '400',
fontSize: '40px',
fontFamily: "WDXL Lubrifont TC, sans-serif",
letterSpacing: '.08em'
}}
onClick={onShiftSyncClick}
>
ShiftSync
</Title>
</LeftContainer>
<RightContainer style={{ position: 'relative' }}>
{navItems?.map((item) => {
if (!item?.hidden) {
return (
<Tab
key={item?.id}
ref={(el) => { tabRefs.current[item.id] = el; }}
onClick={() => onNavButtonClick(item)}
>
{item?.name}
</Tab>
);
}
return null;
}).filter((item) => item !== null)}
<SlidingIndicator style={indicatorStyle} />
</RightContainer>
</NavGroup>
<Border />
</NavBox>
)
}

View file

@ -0,0 +1 @@
export { NavBar } from './NavBar';

View file

@ -0,0 +1,70 @@
import React, { useEffect, useState, useRef } from 'react';
import styled from '@emotion/styled';
import { NavBar } from './NavBar';
import { Footer } from './Footer';
const minHeight = 'calc(100vh - 30px)';
const Body = styled.div({
display: 'flex',
flexDirection: 'column',
position: 'relative',
overflow: 'hidden',
height: '100vh',
minHeight
});
const MainContainer = styled.div({
display: 'flex',
textAlign: 'center',
width: '100%',
flexDirection: 'row',
height: minHeight
});
const Main = styled.div({
background: 'linear-gradient(100deg, #f3f9ff 80%, rgba(217, 217, 217) 100%);',
display: 'flex',
flexDirection: 'column',
width: '100%',
height: minHeight
});
const Content = styled.div(({ cssProps }) => ({
display: 'flex',
flexDirection: 'column',
width: '100%',
alignContent: 'center',
overflowY: 'auto',
height: cssProps?.minHeightMain || '100%'
}));
export const Shell = ({ children }) => {
const bannerRef = useRef();
const nonContentHeight = 92 + (bannerRef?.current?.offsetHeight || 0);
const minHeightMain = `calc(100vh - ${nonContentHeight}px)`;
useEffect(() => {
window.scrollTo(0,0);
}, []);
return (
<Body>
<MainContainer id="main-container">
<Main id="main">
<NavBar />
<div ref={bannerRef} />
<Content
cssProps={{
minHeightMain
}}
id="content"
>
{children}
</Content>
</Main>
</MainContainer>
<Footer />
</Body>
)
}

View file

@ -0,0 +1 @@
export { Shell } from './Shell';

1
components/core/index.js Normal file
View file

@ -0,0 +1 @@
export { Shell } from './Shell';

1
components/index.js Normal file
View file

@ -0,0 +1 @@
export * from './core';

View file

@ -3,6 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=WDXL+Lubrifont+TC&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ShiftSync</title> <title>ShiftSync</title>
</head> </head>

1
package-lock.json generated
View file

@ -17,6 +17,7 @@
"react-router-dom": "^7.6.0" "react-router-dom": "^7.6.0"
}, },
"devDependencies": { "devDependencies": {
"@emotion/babel-plugin": "^11.13.5",
"@eslint/js": "^9.25.0", "@eslint/js": "^9.25.0",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2", "@types/react-dom": "^19.1.2",

View file

@ -19,6 +19,7 @@
"react-router-dom": "^7.6.0" "react-router-dom": "^7.6.0"
}, },
"devDependencies": { "devDependencies": {
"@emotion/babel-plugin": "^11.13.5",
"@eslint/js": "^9.25.0", "@eslint/js": "^9.25.0",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2", "@types/react-dom": "^19.1.2",

1
src/index.js Normal file
View file

@ -0,0 +1 @@
export * from '../components';

View file

@ -1,13 +1,16 @@
import React from 'react'; import React from 'react';
import { Routes, Route } from 'react-router-dom'; import { Routes, Route } from 'react-router-dom';
import { Home, About } from '@src/pages'; import { Home, About } from '@src/pages';
import { Shell } from '../../components';
const AppRouter = () => { const AppRouter = () => {
return ( return (
<Routes> <Shell>
<Route path="/" element={<Home />} /> <Routes>
<Route path="/about" element={<About />} /> <Route path="/" element={<Home />} />
</Routes> <Route path="/about" element={<About />} />
</Routes>
</Shell>
); );
}; };