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>
|
<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
1
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
1
src/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from '../components';
|
||||||
|
|
@ -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 (
|
||||||
|
<Shell>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/about" element={<About />} />
|
<Route path="/about" element={<About />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</Shell>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue