@evenicanpm/ui 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/package.json +36 -0
- package/src/components/button/button.tsx +11 -0
- package/src/components/button/index.tsx +1 -0
- package/src/components/button/styles/index.ts +11 -0
- package/src/components/flex-box/flex-between.tsx +14 -0
- package/src/components/flex-box/flex-box.tsx +29 -0
- package/src/components/flex-box/flex-row-center.tsx +9 -0
- package/src/components/flex-box/index.ts +5 -0
- package/src/components/footer/footer.tsx +96 -0
- package/src/components/footer/index.ts +1 -0
- package/src/components/footer/styles/index.ts +58 -0
- package/src/components/header/header.tsx +50 -0
- package/src/components/header/index.ts +1 -0
- package/src/components/header/styles/index.ts +25 -0
- package/src/components/index.ts +12 -0
- package/src/components/nav-bar/index.ts +1 -0
- package/src/components/nav-bar/nav-bar.tsx +98 -0
- package/src/components/nav-bar/nav-item.tsx +72 -0
- package/src/components/nav-bar/styles/index.ts +65 -0
- package/src/components/responsive-pagination/index.ts +1 -0
- package/src/components/responsive-pagination/responsive-pagination.tsx +60 -0
- package/src/components/search-input/index.ts +1 -0
- package/src/components/search-input/search-input.tsx +28 -0
- package/src/components/site-logo/index.tsx +1 -0
- package/src/components/site-logo/site-logo.tsx +30 -0
- package/src/components/site-logo/styles/index.ts +34 -0
- package/src/components/sticky/Sticky.stories.tsx +89 -0
- package/src/components/sticky/index.ts +1 -0
- package/src/components/sticky/sticky.tsx +67 -0
- package/src/components/sticky/styles.ts +38 -0
- package/src/components/typography/index.ts +1 -0
- package/src/components/typography/typography.tsx +226 -0
- package/src/components/user/index.ts +1 -0
- package/src/components/user/styles/index.ts +20 -0
- package/src/components/user/user.tsx +74 -0
- package/src/index.ts +10 -0
- package/src/theme/build-mui-theme.ts +146 -0
- package/src/theme/components.ts +208 -0
- package/src/theme/index.ts +1 -0
- package/src/theme/theme-colors.ts +205 -0
- package/src/theme/theme-options.ts +113 -0
- package/src/theme/theme-provider.tsx +40 -0
- package/src/theme/utils.ts +17 -0
- package/src/utils/constants.ts +8 -0
- package/src/utils/index.ts +1 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@evenicanpm/ui",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Common and re-usable UI elements for e4 products.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./components": "./src/components/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"main": "./src/index.ts",
|
|
13
|
+
"module": "./src/index.ts",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@emotion/react": "^11.11.3",
|
|
19
|
+
"@emotion/styled": "^11.11.0",
|
|
20
|
+
"@mui/icons-material": "^6.1.7",
|
|
21
|
+
"@mui/material": "^6.1.7",
|
|
22
|
+
"@types/react": "^18.3.24",
|
|
23
|
+
"react": "^19.0.0",
|
|
24
|
+
"typescript": "^5.6.3"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@emotion/react": "^11.11.3",
|
|
28
|
+
"@emotion/styled": "^11.11.0",
|
|
29
|
+
"@mui/icons-material": "^6.1.7",
|
|
30
|
+
"@mui/material": "^6.1.7",
|
|
31
|
+
"@types/react": "^18.3.24",
|
|
32
|
+
"react": "^19.0.0",
|
|
33
|
+
"typescript": "^5.6.3"
|
|
34
|
+
},
|
|
35
|
+
"gitHead": "074f33378006dfc21ceb49456806a18dbb6e91ad"
|
|
36
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ButtonProps } from "@mui/material";
|
|
2
|
+
import { StyledButton } from "./styles";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Very simple button wrapper to extend later
|
|
6
|
+
*/
|
|
7
|
+
const Button = (props: ButtonProps) => {
|
|
8
|
+
return <StyledButton {...props} />;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { Button };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./button";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Button as MuiButton } from "@mui/material";
|
|
2
|
+
import styled from "@mui/material/styles/styled";
|
|
3
|
+
|
|
4
|
+
export const StyledButton = styled(MuiButton)(({ theme }) => ({
|
|
5
|
+
backgroundColor: theme.palette.secondary.main,
|
|
6
|
+
color: theme.palette.common.white,
|
|
7
|
+
|
|
8
|
+
"&:hover": {
|
|
9
|
+
backgroundColor: theme.palette.grey[900],
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Box, { type BoxProps } from "@mui/material/Box";
|
|
2
|
+
|
|
3
|
+
export default function FlexBetween({ children, ...props }: BoxProps) {
|
|
4
|
+
return (
|
|
5
|
+
<Box
|
|
6
|
+
display="flex"
|
|
7
|
+
alignItems="center"
|
|
8
|
+
justifyContent="space-between"
|
|
9
|
+
{...props}
|
|
10
|
+
>
|
|
11
|
+
{children}
|
|
12
|
+
</Box>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Box, { type BoxProps } from "@mui/material/Box";
|
|
2
|
+
|
|
3
|
+
// /**
|
|
4
|
+
// * Convenience utility wrapper around css Flex style
|
|
5
|
+
// * to quickly apply flexbox styles to an mui box
|
|
6
|
+
// */
|
|
7
|
+
// export default function FlexBox({ children, ...props }: BoxProps) {
|
|
8
|
+
// return (
|
|
9
|
+
// <Box display="flex" {...props}>
|
|
10
|
+
// {children}
|
|
11
|
+
// </Box>
|
|
12
|
+
// );
|
|
13
|
+
// }
|
|
14
|
+
|
|
15
|
+
export default function FlexBox({ children, sx, ...props }: BoxProps) {
|
|
16
|
+
return (
|
|
17
|
+
<Box
|
|
18
|
+
{...props}
|
|
19
|
+
display="flex"
|
|
20
|
+
sx={{
|
|
21
|
+
flexDirection: { xs: "column", sm: "row" },
|
|
22
|
+
gap: { xs: 1, sm: 2 },
|
|
23
|
+
...sx,
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</Box>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
7
|
+
import { FlexBox } from "../flex-box";
|
|
8
|
+
import { SiteLogo } from "../site-logo";
|
|
9
|
+
|
|
10
|
+
// STYLED COMPONENTS
|
|
11
|
+
import {
|
|
12
|
+
FooterContent,
|
|
13
|
+
FooterWrapper,
|
|
14
|
+
NavColumn,
|
|
15
|
+
NavGrid,
|
|
16
|
+
NavHeading,
|
|
17
|
+
NavItem,
|
|
18
|
+
StyledContainer,
|
|
19
|
+
} from "./styles";
|
|
20
|
+
|
|
21
|
+
type FooterRootProps = {
|
|
22
|
+
className?: string;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type FooterLogoProps = {
|
|
27
|
+
companyName: string;
|
|
28
|
+
imgSrc: string;
|
|
29
|
+
imgHeight: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type FooterNavItem = {
|
|
33
|
+
label: string;
|
|
34
|
+
href?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type FooterNavSection = {
|
|
38
|
+
title: string;
|
|
39
|
+
items: FooterNavItem[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Footer Wrapper Component
|
|
44
|
+
*/
|
|
45
|
+
const Footer = ({ className, children }: FooterRootProps) => {
|
|
46
|
+
return (
|
|
47
|
+
<FooterWrapper className={clsx(className)}>
|
|
48
|
+
<StyledContainer>
|
|
49
|
+
<FooterContent>{children}</FooterContent>
|
|
50
|
+
</StyledContainer>
|
|
51
|
+
</FooterWrapper>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Left slot – Logo
|
|
57
|
+
*/
|
|
58
|
+
Footer.Logo = ({ companyName, imgSrc, imgHeight }: FooterLogoProps) => {
|
|
59
|
+
return (
|
|
60
|
+
<FlexBox alignItems="flex-start">
|
|
61
|
+
<SiteLogo
|
|
62
|
+
companyName={companyName}
|
|
63
|
+
imgSrc={imgSrc}
|
|
64
|
+
imgHeight={imgHeight}
|
|
65
|
+
/>
|
|
66
|
+
</FlexBox>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Right slot – Navigation
|
|
72
|
+
*/
|
|
73
|
+
Footer.Nav = ({ sections }: { sections: FooterNavSection[] }) => {
|
|
74
|
+
return (
|
|
75
|
+
<NavGrid>
|
|
76
|
+
{sections.map((section) => (
|
|
77
|
+
<NavColumn key={section.title}>
|
|
78
|
+
<NavHeading>{section.title}</NavHeading>
|
|
79
|
+
|
|
80
|
+
{section.items.map((item) => (
|
|
81
|
+
<NavItem
|
|
82
|
+
key={item.label}
|
|
83
|
+
href={item.href}
|
|
84
|
+
target="_blank"
|
|
85
|
+
rel="noopener noreferrer"
|
|
86
|
+
>
|
|
87
|
+
{item.label}
|
|
88
|
+
</NavItem>
|
|
89
|
+
))}
|
|
90
|
+
</NavColumn>
|
|
91
|
+
))}
|
|
92
|
+
</NavGrid>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { Footer };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Footer } from "./footer";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Container from "@mui/material/Container";
|
|
2
|
+
import styled from "@mui/material/styles/styled";
|
|
3
|
+
|
|
4
|
+
export const FooterWrapper = styled("footer")(({ theme }) => ({
|
|
5
|
+
width: "100%",
|
|
6
|
+
borderTop: `1px solid ${theme.palette.secondary.main}`,
|
|
7
|
+
backgroundColor: theme.palette.background.paper,
|
|
8
|
+
paddingTop: theme.spacing(6),
|
|
9
|
+
paddingBottom: theme.spacing(6),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
export const StyledContainer = styled(Container)(({ theme }) => ({
|
|
13
|
+
display: "flex",
|
|
14
|
+
justifyContent: "center",
|
|
15
|
+
paddingTop: theme.spacing(6),
|
|
16
|
+
paddingBottom: theme.spacing(6),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
export const FooterContent = styled("div")(({ theme }) => ({
|
|
20
|
+
display: "flex",
|
|
21
|
+
alignItems: "flex-start",
|
|
22
|
+
columnGap: theme.spacing(4),
|
|
23
|
+
maxWidth: theme.breakpoints.values.lg,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
export const NavColumn = styled("div")(({ theme }) => ({
|
|
27
|
+
display: "flex",
|
|
28
|
+
flexDirection: "column",
|
|
29
|
+
rowGap: theme.spacing(1.5),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
export const NavHeading = styled("div")(({ theme }) => ({
|
|
33
|
+
fontWeight: 600,
|
|
34
|
+
fontSize: 16,
|
|
35
|
+
letterSpacing: "0.08em",
|
|
36
|
+
color: theme.palette.secondary.main,
|
|
37
|
+
textTransform: "uppercase",
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
export const NavItem = styled("a")(({ theme }) => ({
|
|
41
|
+
fontSize: 14,
|
|
42
|
+
color: theme.palette.text.secondary,
|
|
43
|
+
textDecoration: "none",
|
|
44
|
+
cursor: "pointer",
|
|
45
|
+
|
|
46
|
+
"&:hover": {
|
|
47
|
+
color: theme.palette.text.primary,
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
export const NavGrid = styled("div")(({ theme }) => ({
|
|
52
|
+
display: "grid",
|
|
53
|
+
gridTemplateColumns: "repeat(2, minmax(160px, 1fr))",
|
|
54
|
+
gap: theme.spacing(3, 4),
|
|
55
|
+
[theme.breakpoints.down("md")]: {
|
|
56
|
+
gridTemplateColumns: "1fr",
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { FlexBox } from "../flex-box";
|
|
6
|
+
import { SiteLogo } from "../site-logo";
|
|
7
|
+
import { HeaderWrapper, StyledContainer } from "./styles/index";
|
|
8
|
+
|
|
9
|
+
type HeaderRootProps = {
|
|
10
|
+
className?: string;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type HeaderLogoProps = {
|
|
15
|
+
companyName: string;
|
|
16
|
+
imgSrc: string;
|
|
17
|
+
imgHeight: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Header Wrapper Component. Has a logo and two slots to insert content.
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
const Header = ({ className, children }: HeaderRootProps) => {
|
|
25
|
+
return (
|
|
26
|
+
<HeaderWrapper className={clsx(className)}>
|
|
27
|
+
<StyledContainer>{children}</StyledContainer>
|
|
28
|
+
</HeaderWrapper>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
Header.MidSlot = ({ children }: { children: ReactNode }) => {
|
|
33
|
+
return (
|
|
34
|
+
<FlexBox minWidth={100} alignItems="center">
|
|
35
|
+
{children}
|
|
36
|
+
</FlexBox>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
Header.Logo = ({ companyName, imgSrc, imgHeight }: HeaderLogoProps) => {
|
|
41
|
+
return (
|
|
42
|
+
<SiteLogo companyName={companyName} imgSrc={imgSrc} imgHeight={imgHeight} />
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
Header.RightSlot = ({ children }: { children: ReactNode }) => {
|
|
47
|
+
return <>{children}</>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export { Header };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Header } from "./header";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// CONSTANT VARIABLES
|
|
2
|
+
|
|
3
|
+
import Container from "@mui/material/Container";
|
|
4
|
+
import styled from "@mui/material/styles/styled";
|
|
5
|
+
import { layoutConstant } from "../../../utils";
|
|
6
|
+
|
|
7
|
+
export const HeaderWrapper = styled("div")(({ theme }) => ({
|
|
8
|
+
zIndex: 3,
|
|
9
|
+
position: "relative",
|
|
10
|
+
height: layoutConstant.headerHeight,
|
|
11
|
+
transition: "height 250ms ease-in-out",
|
|
12
|
+
background: theme.palette.common.white,
|
|
13
|
+
// borderBottom: `1px solid ${theme.palette.grey[200]}`,
|
|
14
|
+
[theme.breakpoints.down("sm")]: {
|
|
15
|
+
height: layoutConstant.mobileHeaderHeight,
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
export const StyledContainer = styled(Container)({
|
|
20
|
+
gap: 2,
|
|
21
|
+
height: "100%",
|
|
22
|
+
display: "flex",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
justifyContent: "space-between",
|
|
25
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./button";
|
|
2
|
+
export * from "./flex-box";
|
|
3
|
+
export * from "./footer";
|
|
4
|
+
export * from "./header";
|
|
5
|
+
export * from "./nav-bar";
|
|
6
|
+
export * from "./nav-bar";
|
|
7
|
+
export * from "./responsive-pagination";
|
|
8
|
+
export * from "./search-input";
|
|
9
|
+
export * from "./site-logo";
|
|
10
|
+
export * from "./sticky";
|
|
11
|
+
export * from "./typography";
|
|
12
|
+
export * from "./user";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NavBar } from "./nav-bar";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
|
4
|
+
import MenuIcon from "@mui/icons-material/Menu";
|
|
5
|
+
import { Box, IconButton, Menu, MenuItem } from "@mui/material";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import NavItem from "./nav-item";
|
|
8
|
+
import { DesktopNav, MobileOnly, StyledAppBar, StyledToolbar } from "./styles";
|
|
9
|
+
|
|
10
|
+
export interface NavItemConfig {
|
|
11
|
+
label: string;
|
|
12
|
+
href: string;
|
|
13
|
+
children?: NavItemConfig[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface NavBarProps {
|
|
17
|
+
items: NavItemConfig[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const NavBar = ({ items }: NavBarProps) => {
|
|
21
|
+
const [activeMenu, setActiveMenu] = useState<string | null>(null); // desktop active menu
|
|
22
|
+
const [mobileAnchor, setMobileAnchor] = useState<HTMLElement | null>(null);
|
|
23
|
+
const [mobileOpenItem, setMobileOpenItem] = useState<string | null>(null); // mobile open dropdown
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<StyledAppBar position="static" elevation={0}>
|
|
27
|
+
<StyledToolbar>
|
|
28
|
+
{/* ---------------- MOBILE MENU ---------------- */}
|
|
29
|
+
<MobileOnly>
|
|
30
|
+
<IconButton onClick={(e) => setMobileAnchor(e.currentTarget)}>
|
|
31
|
+
<MenuIcon />
|
|
32
|
+
</IconButton>
|
|
33
|
+
|
|
34
|
+
<Menu
|
|
35
|
+
anchorEl={mobileAnchor}
|
|
36
|
+
open={Boolean(mobileAnchor)}
|
|
37
|
+
onClose={() => {
|
|
38
|
+
setMobileAnchor(null);
|
|
39
|
+
setMobileOpenItem(null);
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
{items.map((item) => (
|
|
43
|
+
<Box key={item.label}>
|
|
44
|
+
<MenuItem
|
|
45
|
+
onClick={() => {
|
|
46
|
+
if (item.children) {
|
|
47
|
+
// toggle dropdown
|
|
48
|
+
setMobileOpenItem(
|
|
49
|
+
mobileOpenItem === item.label ? null : item.label,
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
setMobileAnchor(null);
|
|
53
|
+
window.open(item.href, "_blank");
|
|
54
|
+
}
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{item.label}
|
|
58
|
+
{item.children && <ArrowDropDownIcon />}
|
|
59
|
+
</MenuItem>
|
|
60
|
+
|
|
61
|
+
{/* render child links */}
|
|
62
|
+
{item.children &&
|
|
63
|
+
mobileOpenItem === item.label &&
|
|
64
|
+
item.children.map((child) => (
|
|
65
|
+
<MenuItem
|
|
66
|
+
key={child.label}
|
|
67
|
+
sx={{ pl: 4 }}
|
|
68
|
+
onClick={() => {
|
|
69
|
+
setMobileAnchor(null);
|
|
70
|
+
window.open(child.href, "_blank");
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
{child.label}
|
|
74
|
+
</MenuItem>
|
|
75
|
+
))}
|
|
76
|
+
</Box>
|
|
77
|
+
))}
|
|
78
|
+
</Menu>
|
|
79
|
+
</MobileOnly>
|
|
80
|
+
|
|
81
|
+
{/* ---------------- DESKTOP MENU ---------------- */}
|
|
82
|
+
<DesktopNav>
|
|
83
|
+
{items.map((item) => (
|
|
84
|
+
<NavItem
|
|
85
|
+
key={item.label}
|
|
86
|
+
item={item}
|
|
87
|
+
isOpen={activeMenu === item.label}
|
|
88
|
+
onOpen={(label) => setActiveMenu(label)}
|
|
89
|
+
onClose={() => setActiveMenu(null)}
|
|
90
|
+
/>
|
|
91
|
+
))}
|
|
92
|
+
</DesktopNav>
|
|
93
|
+
</StyledToolbar>
|
|
94
|
+
</StyledAppBar>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export default NavBar;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
|
4
|
+
import { Box, MenuItem } from "@mui/material";
|
|
5
|
+
import { type FC, type MouseEvent, useState } from "react";
|
|
6
|
+
import type { NavItemConfig } from "./nav-bar";
|
|
7
|
+
import { NavButton, StyledMenu } from "./styles";
|
|
8
|
+
|
|
9
|
+
interface NavItemProps {
|
|
10
|
+
item: NavItemConfig;
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
onOpen: (label: string) => void;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const NavItem: FC<NavItemProps> = ({ item, isOpen, onOpen, onClose }) => {
|
|
17
|
+
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
18
|
+
|
|
19
|
+
const handleMouseEnter = (e: MouseEvent<HTMLElement>) => {
|
|
20
|
+
if (!item.children) return;
|
|
21
|
+
setAnchorEl(e.currentTarget);
|
|
22
|
+
onOpen(item.label); // set active menu immediately
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleMouseLeave = () => {
|
|
26
|
+
setAnchorEl(null);
|
|
27
|
+
onClose();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
|
32
|
+
<NavButton
|
|
33
|
+
component="a"
|
|
34
|
+
href={item.href}
|
|
35
|
+
target="_blank"
|
|
36
|
+
rel="noopener noreferrer"
|
|
37
|
+
>
|
|
38
|
+
{item.label}
|
|
39
|
+
{item.children && <ArrowDropDownIcon />}
|
|
40
|
+
</NavButton>
|
|
41
|
+
|
|
42
|
+
{item.children && (
|
|
43
|
+
<StyledMenu
|
|
44
|
+
anchorEl={anchorEl}
|
|
45
|
+
open={isOpen}
|
|
46
|
+
onClose={handleMouseLeave}
|
|
47
|
+
MenuListProps={{
|
|
48
|
+
onMouseEnter: () => onOpen(item.label),
|
|
49
|
+
onMouseLeave: handleMouseLeave,
|
|
50
|
+
}}
|
|
51
|
+
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
|
52
|
+
transformOrigin={{ vertical: "top", horizontal: "center" }}
|
|
53
|
+
>
|
|
54
|
+
{item.children.map((child) => (
|
|
55
|
+
<MenuItem
|
|
56
|
+
key={child.label}
|
|
57
|
+
component="a"
|
|
58
|
+
href={child.href}
|
|
59
|
+
target="_blank"
|
|
60
|
+
rel="noopener noreferrer"
|
|
61
|
+
onClick={handleMouseLeave}
|
|
62
|
+
>
|
|
63
|
+
{child.label}
|
|
64
|
+
</MenuItem>
|
|
65
|
+
))}
|
|
66
|
+
</StyledMenu>
|
|
67
|
+
)}
|
|
68
|
+
</Box>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default NavItem;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppBar,
|
|
3
|
+
Box,
|
|
4
|
+
Button,
|
|
5
|
+
type ButtonProps,
|
|
6
|
+
Menu,
|
|
7
|
+
Toolbar,
|
|
8
|
+
} from "@mui/material";
|
|
9
|
+
import { styled } from "@mui/material/styles";
|
|
10
|
+
import type React from "react";
|
|
11
|
+
|
|
12
|
+
export const StyledAppBar = styled(AppBar)(({ theme }) => ({
|
|
13
|
+
backgroundColor: theme.palette.primary.main,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
export const StyledToolbar = styled(Toolbar)({
|
|
17
|
+
position: "relative",
|
|
18
|
+
display: "flex",
|
|
19
|
+
justifyContent: "center",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const DesktopNav = styled(Box)(({ theme }) => ({
|
|
23
|
+
display: "none",
|
|
24
|
+
gap: theme.spacing(3),
|
|
25
|
+
|
|
26
|
+
[theme.breakpoints.up("md")]: {
|
|
27
|
+
display: "flex",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
export const MobileOnly = styled(Box)(({ theme }) => ({
|
|
33
|
+
position: "absolute",
|
|
34
|
+
left: theme.spacing(1),
|
|
35
|
+
|
|
36
|
+
display: "flex",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
|
|
39
|
+
[theme.breakpoints.up("md")]: {
|
|
40
|
+
display: "none",
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
export const NavButton = styled(Button)<
|
|
45
|
+
ButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
46
|
+
>(({ theme }) => ({
|
|
47
|
+
display: "flex",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
gap: theme.spacing(0.5),
|
|
50
|
+
padding: theme.spacing(1.2, 2.5),
|
|
51
|
+
color: theme.palette.secondary.main,
|
|
52
|
+
fontWeight: 500,
|
|
53
|
+
textTransform: "none",
|
|
54
|
+
|
|
55
|
+
"&:hover": {
|
|
56
|
+
backgroundColor: theme.palette.primary.light,
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
export const StyledMenu = styled(Menu)(({ theme }) => ({
|
|
61
|
+
"& .MuiPaper-root": {
|
|
62
|
+
marginTop: theme.spacing(1),
|
|
63
|
+
minWidth: 180,
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ResponsiveTablePagination } from "./responsive-pagination";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type SxProps,
|
|
3
|
+
TablePagination,
|
|
4
|
+
type TablePaginationProps,
|
|
5
|
+
type Theme,
|
|
6
|
+
} from "@mui/material";
|
|
7
|
+
|
|
8
|
+
type Props = TablePaginationProps & {
|
|
9
|
+
sx?: SxProps<Theme>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const responsivePagination: SxProps<Theme> = {
|
|
13
|
+
"& .MuiTablePagination-toolbar": {
|
|
14
|
+
display: { xs: "grid", sm: "flex" },
|
|
15
|
+
gridTemplateColumns: { xs: "auto auto", sm: "unset" },
|
|
16
|
+
gridTemplateRows: { xs: "auto auto", sm: "unset" },
|
|
17
|
+
justifyContent: { xs: "center", sm: "unset" },
|
|
18
|
+
alignItems: "center",
|
|
19
|
+
width: "100%",
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
"& .MuiTablePagination-selectLabel": {
|
|
23
|
+
gridColumn: { xs: "1 / 2", sm: "auto" },
|
|
24
|
+
gridRow: { xs: "1 / 2", sm: "auto" },
|
|
25
|
+
whiteSpace: "nowrap",
|
|
26
|
+
justifySelf: { xs: "center", sm: "auto" },
|
|
27
|
+
textAlign: "center",
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
"& .MuiTablePagination-input": {
|
|
31
|
+
gridColumn: { xs: "2 / 3", sm: "auto" },
|
|
32
|
+
gridRow: { xs: "1 / 2", sm: "auto" },
|
|
33
|
+
justifySelf: { xs: "start", sm: "auto" },
|
|
34
|
+
display: "flex",
|
|
35
|
+
justifyContent: "center",
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
"& .MuiTablePagination-displayedRows": {
|
|
40
|
+
gridColumn: { xs: "1 / 2", sm: "auto" },
|
|
41
|
+
gridRow: { xs: "2 / 3", sm: "auto" },
|
|
42
|
+
whiteSpace: "nowrap",
|
|
43
|
+
justifySelf: { xs: "center", sm: "auto" },
|
|
44
|
+
textAlign: "center",
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
"& .MuiTablePagination-actions": {
|
|
48
|
+
gridColumn: { xs: "2 / 3", sm: "auto" },
|
|
49
|
+
gridRow: { xs: "2 / 3", sm: "auto" },
|
|
50
|
+
marginLeft: { xs: "0 !important", sm: "auto" },
|
|
51
|
+
justifySelf: { xs: "start", sm: "auto" },
|
|
52
|
+
display: "flex",
|
|
53
|
+
justifyContent: "center",
|
|
54
|
+
alignItems: "center",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function ResponsiveTablePagination(props: Props) {
|
|
59
|
+
return <TablePagination {...props} sx={responsivePagination} />;
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./search-input";
|