Create Shell, Add Fonts, Create Navbar, Create Footer
This commit is contained in:
parent
21dbd59e93
commit
9602569ae8
13 changed files with 293 additions and 4 deletions
24
components/core/Shell/Footer/Footer.jsx
Normal file
24
components/core/Shell/Footer/Footer.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
1
components/core/Shell/Footer/index.js
Normal file
1
components/core/Shell/Footer/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { Footer } from './Footer';
|
||||
181
components/core/Shell/NavBar/NavBar.jsx
Normal file
181
components/core/Shell/NavBar/NavBar.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
1
components/core/Shell/NavBar/index.js
Normal file
1
components/core/Shell/NavBar/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { NavBar } from './NavBar';
|
||||
70
components/core/Shell/Shell.jsx
Normal file
70
components/core/Shell/Shell.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
1
components/core/Shell/index.js
Normal file
1
components/core/Shell/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { Shell } from './Shell';
|
||||
1
components/core/index.js
Normal file
1
components/core/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { Shell } from './Shell';
|
||||
1
components/index.js
Normal file
1
components/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './core';
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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" />
|
||||
<title>ShiftSync</title>
|
||||
</head>
|
||||
|
|
|
|||
1
package-lock.json
generated
1
package-lock.json
generated
|
|
@ -17,6 +17,7 @@
|
|||
"react-router-dom": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"react-router-dom": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
|
|
|
|||
1
src/index.js
Normal file
1
src/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from '../components';
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
import React from 'react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { Home, About } from '@src/pages';
|
||||
import { Shell } from '../../components';
|
||||
|
||||
const AppRouter = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
</Routes>
|
||||
<Shell>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
</Routes>
|
||||
</Shell>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue