Skip to content

Commit 3346907

Browse files
authored
Merge pull request #62 from pythonkr/fix/fix-2026-navbar
fix: pyconkr-2026 헤더 네비게이션 레이아웃 개선
2 parents f11d8b7 + 76504a1 commit 3346907

1 file changed

Lines changed: 92 additions & 60 deletions

File tree

  • apps/pyconkr-2026/src/components/layout/Header

apps/pyconkr-2026/src/components/layout/Header/index.tsx

Lines changed: 92 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type NavigationStateType = {
2323

2424
const HeaderHeight: CSSProperties["height"] = "3.625rem";
2525
const BreadCrumbHeight: CSSProperties["height"] = "4.5rem";
26+
const MaxContentWidth: CSSProperties["maxWidth"] = "1366px";
2627

2728
export default function Header() {
2829
const { title, language, siteMapNode, currentSiteMapDepth, shouldShowTitleBanner } = useAppContext();
@@ -49,48 +50,56 @@ export default function Header() {
4950
const headerStyle: SxProps<Theme> = shouldShowTitleBanner ? {} : { backgroundColor: "transparent" };
5051

5152
return (
52-
<Box sx={{ position: "relative" }} onMouseLeave={resetDepths}>
53+
<Box
54+
sx={{
55+
position: "relative",
56+
"&:has(.nav-dropdown:hover) .header-title-text": { opacity: 1 },
57+
}}
58+
onMouseLeave={resetDepths}
59+
>
5360
<HeaderContainer sx={headerStyle}>
54-
<NavSideElementContainer>
55-
<Link to="/" onClick={resetDepths}>
56-
<Stack direction="row" alignItems="center" spacing={0.75}>
57-
<PythonKorea style={{ width: 36, height: 36 }} />
58-
<Typography className="header-title-text" sx={{ color: "#ededde", fontWeight: 600, fontSize: "1rem", letterSpacing: "0.01em" }}>
59-
PyCon Korea 2026
60-
</Typography>
61-
</Stack>
62-
</Link>
63-
</NavSideElementContainer>
61+
<HeaderInner>
62+
<NavSideElementContainer>
63+
<Link to="/" onClick={resetDepths}>
64+
<Stack direction="row" alignItems="center" spacing={0.75}>
65+
<PythonKorea style={{ width: 36, height: 36 }} />
66+
<Typography className="header-title-text" sx={{ color: "#ededde", fontWeight: 600, fontSize: "1rem" }}>
67+
PyCon Korea 2026
68+
</Typography>
69+
</Stack>
70+
</Link>
71+
</NavSideElementContainer>
6472

65-
{siteMapNode ? (
66-
<Stack direction="row" alignItems="center" justifyContent="center" spacing={0.5}>
67-
{Object.values(siteMapNode.children)
68-
.filter((s) => !s.hide)
69-
.map((r) => (
70-
<Link
71-
key={r.id}
72-
onClick={resetDepths}
73-
target={isString(r.external_link) ? "_blank" : undefined}
74-
rel={isString(r.external_link) ? "noopener noreferrer" : undefined}
75-
to={r.external_link || r.route_code}
76-
>
77-
<NavButton onMouseEnter={() => setDepth1(r)} isActive={navState.depth1?.id === r.id}>
78-
{r.name}
79-
</NavButton>
80-
</Link>
81-
))}
82-
</Stack>
83-
) : (
84-
<CircularProgress size={24} sx={{ color: "#ed5ebd" }} />
85-
)}
73+
{siteMapNode ? (
74+
<Stack direction="row" alignItems="center" justifyContent="center" spacing={0.5}>
75+
{Object.values(siteMapNode.children)
76+
.filter((s) => !s.hide)
77+
.map((r) => (
78+
<Link
79+
key={r.id}
80+
onClick={resetDepths}
81+
target={isString(r.external_link) ? "_blank" : undefined}
82+
rel={isString(r.external_link) ? "noopener noreferrer" : undefined}
83+
to={r.external_link || r.route_code}
84+
>
85+
<NavButton onMouseEnter={() => setDepth1(r)} isActive={navState.depth1?.id === r.id}>
86+
{r.name}
87+
</NavButton>
88+
</Link>
89+
))}
90+
</Stack>
91+
) : (
92+
<CircularProgress size={24} sx={{ color: "#ed5ebd" }} />
93+
)}
8694

87-
<NavSideElementContainer sx={{ justifyContent: "flex-end" }}>
88-
<LanguageSelector />
89-
</NavSideElementContainer>
95+
<NavSideElementContainer sx={{ justifyContent: "flex-end" }}>
96+
<LanguageSelector />
97+
</NavSideElementContainer>
98+
</HeaderInner>
9099
</HeaderContainer>
91100

92101
{navState.depth1 && (
93-
<NavDropdownOuter>
102+
<NavDropdownOuter className="nav-dropdown">
94103
<NavDropdownInner>
95104
<Typography variant="h2" sx={{ fontSize: "1.5rem", fontWeight: 700, color: "#ededde" }}>
96105
{navState.depth1.name}
@@ -147,22 +156,24 @@ export default function Header() {
147156
{shouldShowTitleBanner && (
148157
<>
149158
<BreadCrumbContainer>
150-
<Stack direction="row" alignItems="center" spacing={0.5}>
151-
{breadCrumbArray
152-
.filter((routeInfo) => isNonNullish(routeInfo))
153-
.map(({ route_code, name }, index) => {
154-
breadCrumbRoute += `${route_code}/`;
155-
return (
156-
<Fragment key={index}>
157-
{index > 0 && <ArrowForwardIos sx={{ fontSize: "0.75rem", color: "rgba(237,94,189,0.6)" }} />}
158-
<Link to={breadCrumbRoute} children={name} />
159-
</Fragment>
160-
);
161-
})}
162-
</Stack>
163-
<Typography variant="h1" sx={{ fontSize: "1.625rem", fontWeight: 700, color: "#ededde" }}>
164-
{title}
165-
</Typography>
159+
<BreadCrumbInner>
160+
<Stack direction="row" alignItems="center" spacing={0.5}>
161+
{breadCrumbArray
162+
.filter((routeInfo) => isNonNullish(routeInfo))
163+
.map(({ route_code, name }, index) => {
164+
breadCrumbRoute += `${route_code}/`;
165+
return (
166+
<Fragment key={index}>
167+
{index > 0 && <ArrowForwardIos sx={{ fontSize: "0.75rem", color: "rgba(237,94,189,0.6)" }} />}
168+
<Link to={breadCrumbRoute} children={name} />
169+
</Fragment>
170+
);
171+
})}
172+
</Stack>
173+
<Typography variant="h1" sx={{ fontSize: "1.625rem", fontWeight: 700, color: "#ededde" }}>
174+
{title}
175+
</Typography>
176+
</BreadCrumbInner>
166177
</BreadCrumbContainer>
167178
<Box sx={{ height: `calc(${HeaderHeight} + ${BreadCrumbHeight})` }} />
168179
</>
@@ -180,10 +191,6 @@ const ResponsivePadding = ({ theme }: MUIStyledCommonProps) => ({
180191

181192
const HeaderContainer = styled("header")(({ theme }) => ({
182193
position: "fixed",
183-
display: "flex",
184-
flexDirection: "row",
185-
justifyContent: "space-between",
186-
alignItems: "center",
187194
width: "100%",
188195
height: HeaderHeight,
189196
backgroundColor: "rgba(18, 9, 30, 0.85)",
@@ -195,12 +202,25 @@ const HeaderContainer = styled("header")(({ theme }) => ({
195202
zIndex: theme.zIndex.appBar,
196203
transition: "background-color 0.3s ease-in-out",
197204
"& .header-title-text": {
198-
opacity: 0,
205+
// TODO: FIXME: HeaderInner의 좌측 정렬 모드를 중앙 정렬("1fr auto 1fr")로 되돌릴 때 opacity를 다시 0으로 변경할 것 (hover 시에만 노출되는 원래 동작 복귀)
206+
opacity: 1,
199207
transition: "opacity 0.2s ease",
200208
},
201209
"&:hover .header-title-text": {
202210
opacity: 1,
203211
},
212+
}));
213+
214+
const HeaderInner = styled("div")(({ theme }) => ({
215+
display: "grid",
216+
// TODO: FIXME: sitemap 항목이 충분히 등록되면 gridTemplateColumns를 "1fr auto 1fr"로 되돌려 중앙 정렬로 복귀하고, columnGap도 제거할 것
217+
gridTemplateColumns: "auto auto 1fr",
218+
columnGap: theme.spacing(2),
219+
alignItems: "center",
220+
width: "100%",
221+
height: "100%",
222+
maxWidth: MaxContentWidth,
223+
marginInline: "auto",
204224
...ResponsivePadding({ theme }),
205225
}));
206226

@@ -214,7 +234,10 @@ const NavButton = styled(Button)<{ isActive?: boolean }>(({ isActive }) => ({
214234
"&:hover": { color: "#ed5ebd", backgroundColor: "transparent" },
215235
}));
216236

217-
const NavSideElementContainer = styled(Stack)({ flexGrow: 1, flexBasis: 0 });
237+
const NavSideElementContainer = styled(Stack)({
238+
flexDirection: "row",
239+
alignItems: "center",
240+
});
218241

219242
const NavDropdownOuter = styled(Stack)(({ theme }) => ({
220243
width: "100vw",
@@ -231,6 +254,8 @@ const NavDropdownOuter = styled(Stack)(({ theme }) => ({
231254

232255
const NavDropdownInner = styled(Stack)(({ theme }) => ({
233256
width: "100%",
257+
maxWidth: MaxContentWidth,
258+
marginInline: "auto",
234259
minHeight: "10rem",
235260
overflowY: "auto",
236261
gap: "1rem",
@@ -261,7 +286,7 @@ const Depth2to3Divider = styled(Divider)({ borderColor: "rgba(237, 94, 189, 0.3)
261286

262287
const Depth3Item = styled(Depth2Item)({ fontSize: "0.75rem" });
263288

264-
const BreadCrumbContainer = styled(Stack)(({ theme }) => ({
289+
const BreadCrumbContainer = styled("div")(({ theme }) => ({
265290
position: "fixed",
266291
top: HeaderHeight,
267292
width: "100%",
@@ -271,10 +296,17 @@ const BreadCrumbContainer = styled(Stack)(({ theme }) => ({
271296
backdropFilter: "blur(10px)",
272297
WebkitBackdropFilter: "blur(10px)",
273298
borderBottom: "1px solid rgba(237, 94, 189, 0.15)",
299+
zIndex: theme.zIndex.appBar - 1,
300+
}));
301+
302+
const BreadCrumbInner = styled(Stack)(({ theme }) => ({
303+
width: "100%",
304+
height: "100%",
305+
maxWidth: MaxContentWidth,
306+
marginInline: "auto",
274307
gap: "0.25rem",
275308
justifyContent: "center",
276309
alignItems: "flex-start",
277-
zIndex: theme.zIndex.appBar - 1,
278310
...ResponsivePadding({ theme }),
279311
"& a": {
280312
color: "#f5c73d",

0 commit comments

Comments
 (0)