Skip to content

Commit ae0cc8b

Browse files
authored
Merge pull request #38 from pythonkr/feature/mobile-GNB
2 parents 055e291 + e07e636 commit ae0cc8b

File tree

8 files changed

+796
-12
lines changed

8 files changed

+796
-12
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { IconButton, styled } from "@mui/material";
2+
import * as React from "react";
3+
4+
interface HamburgerButtonProps {
5+
isOpen: boolean;
6+
onClick: () => void;
7+
isMainPath?: boolean;
8+
}
9+
10+
export const HamburgerButton: React.FC<HamburgerButtonProps> = ({ isOpen, onClick, isMainPath = true }) => {
11+
return (
12+
<StyledIconButton onClick={onClick} isMainPath={isMainPath}>
13+
<HamburgerIcon isOpen={isOpen} isMainPath={isMainPath}>
14+
<span />
15+
<span />
16+
<span />
17+
</HamburgerIcon>
18+
</StyledIconButton>
19+
);
20+
};
21+
22+
const StyledIconButton = styled(IconButton)<{ isMainPath: boolean }>(({ theme, isMainPath }) => ({
23+
padding: 0,
24+
width: 26,
25+
height: 18,
26+
color: isMainPath ? theme.palette.mobileHeader.main.text : theme.palette.mobileHeader.sub.text,
27+
}));
28+
29+
const HamburgerIcon = styled("div")<{ isOpen: boolean; isMainPath: boolean }>(({ isOpen, theme, isMainPath }) => ({
30+
width: 26,
31+
height: 18,
32+
position: "relative",
33+
cursor: "pointer",
34+
display: "flex",
35+
flexDirection: "column",
36+
justifyContent: "space-between",
37+
38+
"& span": {
39+
display: "block",
40+
height: isOpen ? 3 : 2,
41+
width: "100%",
42+
backgroundColor: isMainPath ? theme.palette.mobileHeader.main.text : theme.palette.mobileHeader.sub.text,
43+
borderRadius: 1,
44+
transition: "height 0.3s ease",
45+
},
46+
}));
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import * as Common from "@frontend/common";
2+
import { Box, Stack, styled, Typography } from "@mui/material";
3+
import * as React from "react";
4+
import { Link, useLocation } from "react-router-dom";
5+
6+
import { HamburgerButton } from "./HamburgerButton";
7+
import { MobileLanguageToggle } from "./MobileLanguageToggle";
8+
import { MobileNavigation } from "./MobileNavigation";
9+
import { useAppContext } from "../../../../contexts/app_context";
10+
11+
interface MobileHeaderProps {
12+
isNavigationOpen?: boolean;
13+
onToggleNavigation?: () => void;
14+
}
15+
16+
export const MobileHeader: React.FC<MobileHeaderProps> = ({ isNavigationOpen = false, onToggleNavigation }) => {
17+
const { siteMapNode } = useAppContext();
18+
const location = useLocation();
19+
const [internalNavigationOpen, setInternalNavigationOpen] = React.useState(false);
20+
21+
const navigationOpen = onToggleNavigation ? isNavigationOpen : internalNavigationOpen;
22+
const toggleNavigation = onToggleNavigation || (() => setInternalNavigationOpen(!internalNavigationOpen));
23+
24+
const isMainPath = location.pathname === "/";
25+
26+
return (
27+
<>
28+
<MobileHeaderContainer isOpen={navigationOpen} isMainPath={isMainPath}>
29+
<LeftContent>
30+
<HamburgerButton isOpen={navigationOpen} onClick={toggleNavigation} isMainPath={isMainPath} />
31+
<LogoAndTextContainer>
32+
<Link to="/" style={{ textDecoration: "none" }}>
33+
<Stack direction="row" alignItems="center" spacing={0.375}>
34+
<Common.Components.PythonKorea style={{ width: 29, height: 29 }} />
35+
<Typography
36+
variant="h6"
37+
sx={{
38+
color: isMainPath ? "white" : "rgba(18, 109, 127, 0.6)",
39+
fontSize: 18,
40+
fontWeight: 600,
41+
}}
42+
>
43+
파이콘 한국 2025
44+
</Typography>
45+
</Stack>
46+
</Link>
47+
</LogoAndTextContainer>
48+
</LeftContent>
49+
50+
<MobileLanguageToggle isMainPath={isMainPath} />
51+
</MobileHeaderContainer>
52+
53+
<MobileNavigation isOpen={navigationOpen} onClose={() => toggleNavigation()} siteMapNode={siteMapNode} />
54+
</>
55+
);
56+
};
57+
58+
const MobileHeaderContainer = styled("header")<{ isOpen: boolean; isMainPath: boolean }>(({ theme, isOpen, isMainPath }) => ({
59+
position: isMainPath ? "fixed" : "sticky",
60+
top: 0,
61+
left: 0,
62+
right: 0,
63+
64+
display: isOpen ? "none" : "flex",
65+
alignItems: "center",
66+
justifyContent: "space-between",
67+
68+
width: "100%",
69+
height: 60,
70+
71+
padding: "15px 23px",
72+
73+
backgroundColor: isMainPath ? "rgba(182, 216, 215, 0.1)" : "#B6D8D7",
74+
backdropFilter: isMainPath ? "blur(8px)" : "none",
75+
WebkitBackdropFilter: isMainPath ? "blur(8px)" : "none",
76+
color: isMainPath ? "white" : "rgba(18, 109, 127, 0.6)",
77+
78+
zIndex: isMainPath ? theme.zIndex.appBar + 100000 : theme.zIndex.appBar,
79+
}));
80+
81+
const LeftContent = styled(Box)({
82+
display: "flex",
83+
alignItems: "center",
84+
gap: 17,
85+
});
86+
87+
const LogoAndTextContainer = styled(Box)({
88+
display: "flex",
89+
alignItems: "center",
90+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { ButtonBase, styled } from "@mui/material";
2+
import * as React from "react";
3+
4+
import { LOCAL_STORAGE_LANGUAGE_KEY } from "../../../../consts/local_stroage";
5+
import { useAppContext } from "../../../../contexts/app_context";
6+
7+
interface MobileLanguageToggleProps {
8+
isMainPath?: boolean;
9+
}
10+
11+
export const MobileLanguageToggle: React.FC<MobileLanguageToggleProps> = ({ isMainPath = true }) => {
12+
const { language, setAppContext } = useAppContext();
13+
14+
const handleLanguageChange = (newLanguage: "ko" | "en") => {
15+
localStorage.setItem(LOCAL_STORAGE_LANGUAGE_KEY, newLanguage);
16+
setAppContext((ps) => ({ ...ps, language: newLanguage }));
17+
};
18+
return (
19+
<ToggleContainer isMainPath={isMainPath}>
20+
<LanguageButton isActive={language === "ko"} isMainPath={isMainPath} onClick={() => handleLanguageChange("ko")}>
21+
KO
22+
</LanguageButton>
23+
<LanguageButton isActive={language === "en"} isMainPath={isMainPath} onClick={() => handleLanguageChange("en")}>
24+
EN
25+
</LanguageButton>
26+
</ToggleContainer>
27+
);
28+
};
29+
30+
const ToggleContainer = styled("div")<{ isMainPath: boolean }>(({ theme, isMainPath }) => ({
31+
display: "flex",
32+
width: 94,
33+
height: 29,
34+
border: "1px solid white",
35+
borderRadius: 15,
36+
padding: 2,
37+
gap: 2,
38+
backgroundColor: isMainPath
39+
? theme.palette.mobileNavigation.main.languageToggle.background
40+
: theme.palette.mobileNavigation.sub.languageToggle.background,
41+
}));
42+
43+
const LanguageButton = styled(ButtonBase)<{ isActive: boolean; isMainPath: boolean }>(({ theme, isActive, isMainPath }) => ({
44+
flex: 1,
45+
height: "100%",
46+
borderRadius: 13,
47+
fontSize: 12,
48+
fontWeight: 400,
49+
transition: "all 0.2s ease",
50+
51+
color: isMainPath ? theme.palette.mobileHeader.main.text : theme.palette.mobileHeader.sub.text,
52+
backgroundColor: "transparent",
53+
54+
...(isActive && {
55+
backgroundColor: isMainPath
56+
? theme.palette.mobileNavigation.main.languageToggle.active.background
57+
: theme.palette.mobileNavigation.sub.languageToggle.active.background,
58+
color: isMainPath ? theme.palette.mobileHeader.main.activeLanguage : theme.palette.mobileHeader.sub.activeLanguage,
59+
fontWeight: 600,
60+
}),
61+
62+
"&:hover": {
63+
backgroundColor: isActive
64+
? isMainPath
65+
? theme.palette.mobileNavigation.main.languageToggle.active.hover
66+
: theme.palette.mobileNavigation.sub.languageToggle.active.hover
67+
: isMainPath
68+
? theme.palette.mobileNavigation.main.languageToggle.inactive.hover
69+
: theme.palette.mobileNavigation.sub.languageToggle.inactive.hover,
70+
},
71+
72+
WebkitFontSmoothing: "antialiased",
73+
MozOsxFontSmoothing: "grayscale",
74+
textRendering: "optimizeLegibility",
75+
WebkitTextStroke: "0.5px transparent",
76+
}));

0 commit comments

Comments
 (0)