From a5f090171e62213972f3fd172fd25c2053249786 Mon Sep 17 00:00:00 2001 From: y00eunji Date: Wed, 18 Jun 2025 14:24:53 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat=20:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20GN?= =?UTF-8?q?B=20=EB=A7=88=ED=81=AC=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/Header/Mobile/HamburgerButton.tsx | 46 +++ .../layout/Header/Mobile/MobileHeader.tsx | 95 +++++ .../Header/Mobile/MobileLanguageToggle.tsx | 65 ++++ .../layout/Header/Mobile/MobileNavigation.tsx | 354 ++++++++++++++++++ .../src/components/layout/Header/index.tsx | 9 +- .../components/layout/SignInButton/index.tsx | 57 ++- apps/pyconkr/src/styles/globalStyles.ts | 12 + types/emotion.d.ts | 24 ++ 8 files changed, 650 insertions(+), 12 deletions(-) create mode 100644 apps/pyconkr/src/components/layout/Header/Mobile/HamburgerButton.tsx create mode 100644 apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx create mode 100644 apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx create mode 100644 apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/HamburgerButton.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/HamburgerButton.tsx new file mode 100644 index 0000000..c859d12 --- /dev/null +++ b/apps/pyconkr/src/components/layout/Header/Mobile/HamburgerButton.tsx @@ -0,0 +1,46 @@ +import { IconButton, styled } from "@mui/material"; +import * as React from "react"; + +interface HamburgerButtonProps { + isOpen: boolean; + onClick: () => void; + isMainPath?: boolean; +} + +export const HamburgerButton: React.FC = ({ isOpen, onClick, isMainPath = true }) => { + return ( + + + + + + + + ); +}; + +const StyledIconButton = styled(IconButton)<{ isMainPath: boolean }>(({ theme, isMainPath }) => ({ + padding: 0, + width: 26, + height: 18, + color: isMainPath ? theme.palette.mobileHeader.main.text : theme.palette.mobileHeader.sub.text, +})); + +const HamburgerIcon = styled("div")<{ isOpen: boolean; isMainPath: boolean }>(({ isOpen, theme, isMainPath }) => ({ + width: 26, + height: 18, + position: "relative", + cursor: "pointer", + display: "flex", + flexDirection: "column", + justifyContent: "space-between", + + "& span": { + display: "block", + height: isOpen ? 3 : 2, + width: "100%", + backgroundColor: isMainPath ? theme.palette.mobileHeader.main.text : theme.palette.mobileHeader.sub.text, + borderRadius: 1, + transition: "height 0.3s ease", + }, +})); diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx new file mode 100644 index 0000000..37206f2 --- /dev/null +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx @@ -0,0 +1,95 @@ +import * as Common from "@frontend/common"; +import { Box, Stack, styled, Typography } from "@mui/material"; +import * as React from "react"; +import { Link, useLocation } from "react-router-dom"; + +import { HamburgerButton } from "./HamburgerButton"; +import { MobileLanguageToggle } from "./MobileLanguageToggle"; +import { MobileNavigation } from "./MobileNavigation"; +import { useAppContext } from "../../../../contexts/app_context"; + +interface MobileHeaderProps { + isNavigationOpen?: boolean; + onToggleNavigation?: () => void; +} + +export const MobileHeader: React.FC = ({ isNavigationOpen = false, onToggleNavigation }) => { + const { siteMapNode, language } = useAppContext(); + const location = useLocation(); + const [internalNavigationOpen, setInternalNavigationOpen] = React.useState(false); + + const navigationOpen = onToggleNavigation ? isNavigationOpen : internalNavigationOpen; + const toggleNavigation = onToggleNavigation || (() => setInternalNavigationOpen(!internalNavigationOpen)); + + const isMainPath = location.pathname === "/"; + + const handleLanguageChange = (newLanguage: string) => { + // TODO: 언어 변경 로직 구현 + console.log("Language changed to:", newLanguage); + }; + + return ( + <> + + + + + + + + + 파이콘 한국 2025 + + + + + + + + + + toggleNavigation()} siteMapNode={siteMapNode} /> + + ); +}; + +const MobileHeaderContainer = styled("header")<{ isOpen: boolean; isMainPath: boolean }>(({ theme, isOpen, isMainPath }) => ({ + position: "fixed", + top: 0, + left: 0, + right: 0, + + display: isOpen ? "none" : "flex", + alignItems: "center", + justifyContent: "space-between", + + width: "100%", + height: 60, + + padding: "15px 23px", + + backgroundColor: isMainPath ? "rgba(182, 216, 215, 0.1)" : "#B6D8D7", + backdropFilter: "blur(8px)", + WebkitBackdropFilter: "blur(8px)", + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.6)", + + zIndex: theme.zIndex.appBar + 100000, +})); + +const LeftContent = styled(Box)({ + display: "flex", + alignItems: "center", + gap: 17, +}); + +const LogoAndTextContainer = styled(Box)({ + display: "flex", + alignItems: "center", +}); diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx new file mode 100644 index 0000000..da5d616 --- /dev/null +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx @@ -0,0 +1,65 @@ +import { ButtonBase, styled } from "@mui/material"; +import * as React from "react"; + +interface MobileLanguageToggleProps { + currentLanguage: string; + onLanguageChange: (newLanguage: string) => void; + isMainPath?: boolean; +} + +export const MobileLanguageToggle: React.FC = ({ currentLanguage, onLanguageChange, isMainPath = true }) => { + return ( + + onLanguageChange("ko")}> + KO + + onLanguageChange("en")}> + EN + + + ); +}; + +const ToggleContainer = styled("div")<{ isMainPath: boolean }>(({ isMainPath }) => ({ + display: "flex", + width: 94, + height: 29, + border: "1px solid white", + borderRadius: 15, + padding: 2, + gap: 2, + backgroundColor: isMainPath ? "transparent" : "rgba(255, 255, 255, 0.1)", +})); + +const LanguageButton = styled(ButtonBase)<{ isActive: boolean; isMainPath: boolean }>(({ isActive, isMainPath }) => ({ + flex: 1, + height: "100%", + borderRadius: 13, + fontSize: 12, + fontWeight: 400, + transition: "all 0.2s ease", + + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.6)", + backgroundColor: "transparent", + + ...(isActive && { + backgroundColor: isMainPath ? "rgba(255, 255, 255, 0.7)" : "rgba(255, 255, 255, 0.9)", + color: isMainPath ? "#888888" : "#126D7F", + fontWeight: 600, + }), + + "&:hover": { + backgroundColor: isActive + ? isMainPath + ? "rgba(255, 255, 255, 0.8)" + : "rgba(255, 255, 255, 1)" + : isMainPath + ? "rgba(255, 255, 255, 0.1)" + : "rgba(255, 255, 255, 0.3)", + }, + + WebkitFontSmoothing: "antialiased", + MozOsxFontSmoothing: "grayscale", + textRendering: "optimizeLegibility", + WebkitTextStroke: "0.5px transparent", +})); diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx new file mode 100644 index 0000000..7d1ec70 --- /dev/null +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx @@ -0,0 +1,354 @@ +import * as Common from "@frontend/common"; +import BackendAPISchemas from "@frontend/common/src/schemas/backendAPI"; +import { ArrowBack, ArrowForward } from "@mui/icons-material"; +import { Box, Button, Chip, Drawer, IconButton, Stack, styled, Typography } from "@mui/material"; +import * as React from "react"; +import { Link, useLocation } from "react-router-dom"; +import * as R from "remeda"; + +import { HamburgerButton } from "./HamburgerButton"; +import { MobileLanguageToggle } from "./MobileLanguageToggle"; +import { useAppContext } from "../../../../contexts/app_context"; +import { SignInButton } from "../../SignInButton"; + +type MenuType = BackendAPISchemas.NestedSiteMapSchema; + +interface MobileNavigationProps { + isOpen: boolean; + onClose: () => void; + siteMapNode?: MenuType; +} + +type NavigationLevel = "depth1" | "depth2" | "depth3"; + +interface NavigationState { + level: NavigationLevel; + depth1?: MenuType; + depth2?: MenuType; + breadcrumbs: { name: string; level: NavigationLevel }[]; +} + +export const MobileNavigation: React.FC = ({ isOpen, onClose, siteMapNode }) => { + const { language } = useAppContext(); + const location = useLocation(); + const [navState, setNavState] = React.useState({ + level: "depth1", + breadcrumbs: [], + }); + + const isMainPath = location.pathname === "/"; + + const resetNavigation = () => { + setNavState({ + level: "depth1", + breadcrumbs: [], + }); + }; + + const navigateToDepth2 = (depth1: MenuType) => { + setNavState({ + level: "depth2", + depth1, + breadcrumbs: [{ name: depth1.name, level: "depth1" }], + }); + }; + + const navigateToDepth3 = (depth2: MenuType) => { + setNavState((prev) => ({ + ...prev, + level: "depth3", + depth2, + breadcrumbs: [...prev.breadcrumbs, { name: depth2.name, level: "depth2" }], + })); + }; + + const goBack = () => { + if (navState.level === "depth3") { + setNavState((prev) => ({ + ...prev, + level: "depth2", + depth2: undefined, + breadcrumbs: prev.breadcrumbs.slice(0, -1), + })); + } else if (navState.level === "depth2") { + resetNavigation(); + } + }; + + const handleClose = () => { + onClose(); + resetNavigation(); + }; + + const handleLanguageChange = (newLanguage: string) => { + // TODO: 언어 변경 로직 구현 + console.log("Language changed to:", newLanguage); + }; + + const renderDepth1Menu = () => { + if (!siteMapNode) return null; + + return ( + + {Object.values(siteMapNode.children) + .filter((s) => !s.hide) + .map((menu) => ( + + + {menu.name} + + {!R.isEmpty(menu.children) && ( + navigateToDepth2(menu)}> + + + )} + + ))} + + ); + }; + + const renderDepth2Menu = () => { + if (!navState.depth1) return null; + + return ( + + + + + + {navState.depth1.name} + + + + + + {Object.values(navState.depth1.children) + .filter((s) => !s.hide) + .map((menu) => ( + + + + + {!R.isEmpty(menu.children) && ( + navigateToDepth3(menu)}> + + + )} + + ))} + + + ); + }; + + const renderDepth3Menu = () => { + if (!navState.depth2) return null; + + return ( + + + + + + {navState.depth2.name} + + + + + + {Object.values(navState.depth2.children) + .filter((s) => !s.hide) + .map((menu) => ( + + + + ))} + + + ); + }; + + return ( + + + + + + + + + + 파이콘 한국 2025 + + + + + + + {navState.level === "depth1" && renderDepth1Menu()} + {navState.level === "depth2" && renderDepth2Menu()} + {navState.level === "depth3" && renderDepth3Menu()} + + + + + + + + + + + ); +}; + +const StyledDrawer = styled(Drawer)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ + "& .MuiDrawer-paper": { + width: "70vw", + background: isMainPath + ? `linear-gradient(0deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)), + linear-gradient(0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15))` + : "#B6D8D7", + backdropFilter: isMainPath ? "blur(10px)" : "none", + WebkitBackdropFilter: isMainPath ? "blur(10px)" : "none", + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + borderTopRightRadius: 15, + borderBottomRightRadius: 15, + }, +})); + +const DrawerContent = styled(Box)({ + height: "100%", + display: "flex", + flexDirection: "column", +}); + +const NavigationHeader = styled(Box)<{ isMainPath: boolean }>({ + display: "flex", + alignItems: "center", + padding: "23px 23px 10px 23px", + position: "relative", + gap: 17, +}); + +const NavigationContent = styled(Box)({ + flex: 1, + overflow: "auto", +}); + +const MenuContainer = styled(Stack)({ + padding: "20px 0", + gap: "25px", +}); + +const MenuItem = styled(Box)<{ isMainPath?: boolean }>({ + display: "flex", + alignItems: "center", + padding: "0 23px", + gap: 23, +}); + +const MenuLink = styled(Link)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + textDecoration: "none", + fontSize: "20px", + fontWeight: 600, +})); + +const MenuArrowButton = styled(IconButton)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + padding: 8, +})); + +const BackButton = styled(Button)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ + display: "flex", + alignItems: "center", + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + textTransform: "none", + padding: "0 15px 0 0", + minWidth: "auto", + minHeight: "auto", +})); + +const MenuChip = styled(Chip)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ + backgroundColor: isMainPath ? "rgba(212, 212, 212, 0.5)" : "rgba(18, 109, 127, 0.2)", + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + height: 40, + borderRadius: 15, + padding: "10px 13px", + fontSize: "16px", + fontWeight: 600, + + "& .MuiChip-label": { + padding: 0, + }, + + "&:hover": { + backgroundColor: isMainPath ? "rgba(212, 212, 212, 0.7)" : "rgba(18, 109, 127, 0.3)", + }, +})); + +const BottomActions = styled(Stack)<{ isMainPath: boolean }>({ + padding: "20px 23px", + gap: 50, + alignItems: "center", +}); + +const HeaderTitle = styled(Typography)<{ isMainPath: boolean }>(({ theme, isMainPath }) => ({ + color: isMainPath ? theme.palette.mobileHeader.main.text : theme.palette.mobileHeader.sub.text, + fontSize: 18, + fontWeight: 600, +})); + +const LogoAndTextContainer = styled(Box)({ + display: "flex", + alignItems: "center", +}); + +const NavigationMenuSection = styled(Box)({ + padding: "20px 23px", +}); + +const Depth2Header = styled(Box)<{ isMainPath: boolean }>({ + display: "flex", + alignItems: "center", + height: "auto", + marginBottom: 10, +}); + +const Depth2Title = styled(Typography)<{ isMainPath: boolean }>(({ isMainPath }) => ({ + color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + fontSize: 20, + fontWeight: 800, +})); + +const Depth2Divider = styled(Box)<{ isMainPath: boolean }>(({ isMainPath }) => ({ + height: 1, + backgroundColor: isMainPath ? "rgba(255, 255, 255, 0.3)" : "rgba(18, 109, 127, 0.3)", + marginBottom: 21, +})); + +const Depth2MenuList = styled(Stack)({ + gap: 15, +}); + +const Depth2MenuItem = styled(Box)({ + display: "flex", + alignItems: "center", + gap: 10, +}); + +const Depth3MenuGrid = styled(Box)({ + height: 260, + display: "flex", + flexDirection: "column", + flexWrap: "wrap", + alignContent: "flex-start", + gap: 15, + overflow: "hidden", +}); diff --git a/apps/pyconkr/src/components/layout/Header/index.tsx b/apps/pyconkr/src/components/layout/Header/index.tsx index 6219cf2..87dcfc1 100644 --- a/apps/pyconkr/src/components/layout/Header/index.tsx +++ b/apps/pyconkr/src/components/layout/Header/index.tsx @@ -1,6 +1,6 @@ import * as Common from "@frontend/common"; import { ArrowForwardIos } from "@mui/icons-material"; -import { Box, Button, CircularProgress, Divider, Stack, styled, SxProps, Theme, Typography } from "@mui/material"; +import { Box, Button, CircularProgress, Divider, Stack, styled, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material"; import { MUIStyledCommonProps } from "@mui/system"; import * as React from "react"; import { Link } from "react-router-dom"; @@ -11,6 +11,7 @@ import { useAppContext } from "../../../contexts/app_context"; import { CartBadgeButton } from "../CartBadgeButton"; import LanguageSelector from "../LanguageSelector"; import { SignInButton } from "../SignInButton"; +import { MobileHeader } from "./Mobile/MobileHeader"; type MenuType = BackendAPISchemas.NestedSiteMapSchema; type MenuOrUndefinedType = MenuType | undefined; @@ -26,6 +27,8 @@ const BreadCrumbHeight: React.CSSProperties["height"] = "4.5rem"; const Header: React.FC = () => { const { title, language, siteMapNode, currentSiteMapDepth, shouldShowTitleBanner } = useAppContext(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); const [navState, setNavState] = React.useState({}); const resetDepths = () => setNavState({}); @@ -38,6 +41,10 @@ const Header: React.FC = () => { React.useEffect(resetDepths, [language]); + if (isMobile) { + return ; + } + let breadCrumbRoute = ""; let breadCrumbArray = currentSiteMapDepth.slice(1, -1); if (R.isEmpty(breadCrumbArray)) breadCrumbArray = currentSiteMapDepth.slice(0, -1); diff --git a/apps/pyconkr/src/components/layout/SignInButton/index.tsx b/apps/pyconkr/src/components/layout/SignInButton/index.tsx index 112a0a3..ad87538 100644 --- a/apps/pyconkr/src/components/layout/SignInButton/index.tsx +++ b/apps/pyconkr/src/components/layout/SignInButton/index.tsx @@ -1,5 +1,6 @@ import * as Shop from "@frontend/shop"; -import { Button } from "@mui/material"; +import { Login } from "@mui/icons-material"; +import { Button, Stack } from "@mui/material"; import { ErrorBoundary, Suspense } from "@suspensive/react"; import { useNavigate } from "react-router-dom"; @@ -9,15 +10,45 @@ type InnerSignInButtonImplPropType = { loading?: boolean; signedIn?: boolean; onSignOut?: () => void; + isMobile?: boolean; + isMainPath?: boolean; }; -const InnerSignInButtonImpl: React.FC = ({ loading, signedIn, onSignOut }) => { +const InnerSignInButtonImpl: React.FC = ({ loading, signedIn, onSignOut, isMobile = false, isMainPath = true }) => { const navigate = useNavigate(); const { language } = useAppContext(); const signInBtnStr = language === "ko" ? "로그인" : "Sign In"; const signOutBtnStr = language === "ko" ? "로그아웃" : "Sign Out"; + if (isMobile) { + return ( + + ); + } + return ( @@ -60,15 +77,27 @@ const InnerSignInButtonImpl: React.FC = ({ loadin ); }; -export const SignInButton: React.FC<{ isMobile?: boolean; isMainPath?: boolean }> = ({ isMobile = false, isMainPath = true }) => { +export const SignInButton: React.FC<{ isMobile?: boolean; isMainPath?: boolean; onClose?: () => void }> = ({ + isMobile = false, + isMainPath = true, + onClose, +}) => { const SignInWithErrorBoundary = ErrorBoundary.with( - { fallback: }, - Suspense.with({ fallback: }, () => { + { fallback: }, + Suspense.with({ fallback: }, () => { const shopAPIClient = Shop.Hooks.useShopClient(); const signOutMutation = Shop.Hooks.useSignOutMutation(shopAPIClient); const { data } = Shop.Hooks.useUserStatus(shopAPIClient); - return ; + return ( + + ); }) ); From 4bc0be59a61c030efd9f2600389e1bcfed5e3c3a Mon Sep 17 00:00:00 2001 From: y00eunji Date: Wed, 18 Jun 2025 15:25:35 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat=20:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20UI?= =?UTF-8?q?=20=EC=96=B8=EC=96=B4=20=EC=A0=84=ED=99=98=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/Header/Mobile/MobileHeader.tsx | 9 ++------- .../Header/Mobile/MobileLanguageToggle.tsx | 17 ++++++++++++----- .../layout/Header/Mobile/MobileNavigation.tsx | 9 +-------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx index 37206f2..51b22a3 100644 --- a/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx @@ -14,7 +14,7 @@ interface MobileHeaderProps { } export const MobileHeader: React.FC = ({ isNavigationOpen = false, onToggleNavigation }) => { - const { siteMapNode, language } = useAppContext(); + const { siteMapNode } = useAppContext(); const location = useLocation(); const [internalNavigationOpen, setInternalNavigationOpen] = React.useState(false); @@ -23,11 +23,6 @@ export const MobileHeader: React.FC = ({ isNavigationOpen = f const isMainPath = location.pathname === "/"; - const handleLanguageChange = (newLanguage: string) => { - // TODO: 언어 변경 로직 구현 - console.log("Language changed to:", newLanguage); - }; - return ( <> @@ -52,7 +47,7 @@ export const MobileHeader: React.FC = ({ isNavigationOpen = f - + toggleNavigation()} siteMapNode={siteMapNode} /> diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx index da5d616..a0d7f71 100644 --- a/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx @@ -1,19 +1,26 @@ import { ButtonBase, styled } from "@mui/material"; import * as React from "react"; +import { LOCAL_STORAGE_LANGUAGE_KEY } from "../../../../consts/local_stroage"; +import { useAppContext } from "../../../../contexts/app_context"; + interface MobileLanguageToggleProps { - currentLanguage: string; - onLanguageChange: (newLanguage: string) => void; isMainPath?: boolean; } -export const MobileLanguageToggle: React.FC = ({ currentLanguage, onLanguageChange, isMainPath = true }) => { +export const MobileLanguageToggle: React.FC = ({ isMainPath = true }) => { + const { language, setAppContext } = useAppContext(); + + const handleLanguageChange = (newLanguage: "ko" | "en") => { + localStorage.setItem(LOCAL_STORAGE_LANGUAGE_KEY, newLanguage); + setAppContext((ps) => ({ ...ps, language: newLanguage })); + }; return ( - onLanguageChange("ko")}> + handleLanguageChange("ko")}> KO - onLanguageChange("en")}> + handleLanguageChange("en")}> EN diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx index 7d1ec70..edea341 100644 --- a/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx @@ -8,7 +8,6 @@ import * as R from "remeda"; import { HamburgerButton } from "./HamburgerButton"; import { MobileLanguageToggle } from "./MobileLanguageToggle"; -import { useAppContext } from "../../../../contexts/app_context"; import { SignInButton } from "../../SignInButton"; type MenuType = BackendAPISchemas.NestedSiteMapSchema; @@ -29,7 +28,6 @@ interface NavigationState { } export const MobileNavigation: React.FC = ({ isOpen, onClose, siteMapNode }) => { - const { language } = useAppContext(); const location = useLocation(); const [navState, setNavState] = React.useState({ level: "depth1", @@ -80,11 +78,6 @@ export const MobileNavigation: React.FC = ({ isOpen, onCl resetNavigation(); }; - const handleLanguageChange = (newLanguage: string) => { - // TODO: 언어 변경 로직 구현 - console.log("Language changed to:", newLanguage); - }; - const renderDepth1Menu = () => { if (!siteMapNode) return null; @@ -198,7 +191,7 @@ export const MobileNavigation: React.FC = ({ isOpen, onCl - + From 91a3950115dec908c00073cf1639e3e64cb01e8e Mon Sep 17 00:00:00 2001 From: y00eunji Date: Wed, 18 Jun 2025 15:33:24 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20UI=20?= =?UTF-8?q?=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EB=A7=88=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Header/Mobile/MobileLanguageToggle.tsx | 24 +++--- .../layout/Header/Mobile/MobileNavigation.tsx | 37 ++++----- apps/pyconkr/src/styles/globalStyles.ts | 41 ++++++++++ types/emotion.d.ts | 80 +++++++++++++++++++ 4 files changed, 152 insertions(+), 30 deletions(-) diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx index a0d7f71..f38217f 100644 --- a/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileLanguageToggle.tsx @@ -27,7 +27,7 @@ export const MobileLanguageToggle: React.FC = ({ isMa ); }; -const ToggleContainer = styled("div")<{ isMainPath: boolean }>(({ isMainPath }) => ({ +const ToggleContainer = styled("div")<{ isMainPath: boolean }>(({ theme, isMainPath }) => ({ display: "flex", width: 94, height: 29, @@ -35,10 +35,12 @@ const ToggleContainer = styled("div")<{ isMainPath: boolean }>(({ isMainPath }) borderRadius: 15, padding: 2, gap: 2, - backgroundColor: isMainPath ? "transparent" : "rgba(255, 255, 255, 0.1)", + backgroundColor: isMainPath + ? theme.palette.mobileNavigation.main.languageToggle.background + : theme.palette.mobileNavigation.sub.languageToggle.background, })); -const LanguageButton = styled(ButtonBase)<{ isActive: boolean; isMainPath: boolean }>(({ isActive, isMainPath }) => ({ +const LanguageButton = styled(ButtonBase)<{ isActive: boolean; isMainPath: boolean }>(({ theme, isActive, isMainPath }) => ({ flex: 1, height: "100%", borderRadius: 13, @@ -46,23 +48,25 @@ const LanguageButton = styled(ButtonBase)<{ isActive: boolean; isMainPath: boole fontWeight: 400, transition: "all 0.2s ease", - color: isMainPath ? "white" : "rgba(18, 109, 127, 0.6)", + color: isMainPath ? theme.palette.mobileHeader.main.text : theme.palette.mobileHeader.sub.text, backgroundColor: "transparent", ...(isActive && { - backgroundColor: isMainPath ? "rgba(255, 255, 255, 0.7)" : "rgba(255, 255, 255, 0.9)", - color: isMainPath ? "#888888" : "#126D7F", + backgroundColor: isMainPath + ? theme.palette.mobileNavigation.main.languageToggle.active.background + : theme.palette.mobileNavigation.sub.languageToggle.active.background, + color: isMainPath ? theme.palette.mobileHeader.main.activeLanguage : theme.palette.mobileHeader.sub.activeLanguage, fontWeight: 600, }), "&:hover": { backgroundColor: isActive ? isMainPath - ? "rgba(255, 255, 255, 0.8)" - : "rgba(255, 255, 255, 1)" + ? theme.palette.mobileNavigation.main.languageToggle.active.hover + : theme.palette.mobileNavigation.sub.languageToggle.active.hover : isMainPath - ? "rgba(255, 255, 255, 0.1)" - : "rgba(255, 255, 255, 0.3)", + ? theme.palette.mobileNavigation.main.languageToggle.inactive.hover + : theme.palette.mobileNavigation.sub.languageToggle.inactive.hover, }, WebkitFontSmoothing: "antialiased", diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx index edea341..21ef26b 100644 --- a/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx @@ -200,16 +200,13 @@ export const MobileNavigation: React.FC = ({ isOpen, onCl ); }; -const StyledDrawer = styled(Drawer)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ +const StyledDrawer = styled(Drawer)<{ isMainPath?: boolean }>(({ theme, isMainPath = true }) => ({ "& .MuiDrawer-paper": { width: "70vw", - background: isMainPath - ? `linear-gradient(0deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)), - linear-gradient(0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15))` - : "#B6D8D7", + background: isMainPath ? theme.palette.mobileNavigation.main.background : theme.palette.mobileNavigation.sub.background, backdropFilter: isMainPath ? "blur(10px)" : "none", WebkitBackdropFilter: isMainPath ? "blur(10px)" : "none", - color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + color: isMainPath ? theme.palette.mobileNavigation.main.text : theme.palette.mobileNavigation.sub.text, borderTopRightRadius: 15, borderBottomRightRadius: 15, }, @@ -246,31 +243,31 @@ const MenuItem = styled(Box)<{ isMainPath?: boolean }>({ gap: 23, }); -const MenuLink = styled(Link)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ - color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", +const MenuLink = styled(Link)<{ isMainPath?: boolean }>(({ theme, isMainPath = true }) => ({ + color: isMainPath ? theme.palette.mobileNavigation.main.text : theme.palette.mobileNavigation.sub.text, textDecoration: "none", fontSize: "20px", fontWeight: 600, })); -const MenuArrowButton = styled(IconButton)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ - color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", +const MenuArrowButton = styled(IconButton)<{ isMainPath?: boolean }>(({ theme, isMainPath = true }) => ({ + color: isMainPath ? theme.palette.mobileNavigation.main.text : theme.palette.mobileNavigation.sub.text, padding: 8, })); -const BackButton = styled(Button)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ +const BackButton = styled(Button)<{ isMainPath?: boolean }>(({ theme, isMainPath = true }) => ({ display: "flex", alignItems: "center", - color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", + color: isMainPath ? theme.palette.mobileNavigation.main.text : theme.palette.mobileNavigation.sub.text, textTransform: "none", padding: "0 15px 0 0", minWidth: "auto", minHeight: "auto", })); -const MenuChip = styled(Chip)<{ isMainPath?: boolean }>(({ isMainPath = true }) => ({ - backgroundColor: isMainPath ? "rgba(212, 212, 212, 0.5)" : "rgba(18, 109, 127, 0.2)", - color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", +const MenuChip = styled(Chip)<{ isMainPath?: boolean }>(({ theme, isMainPath = true }) => ({ + backgroundColor: isMainPath ? theme.palette.mobileNavigation.main.chip.background : theme.palette.mobileNavigation.sub.chip.background, + color: isMainPath ? theme.palette.mobileNavigation.main.text : theme.palette.mobileNavigation.sub.text, height: 40, borderRadius: 15, padding: "10px 13px", @@ -282,7 +279,7 @@ const MenuChip = styled(Chip)<{ isMainPath?: boolean }>(({ isMainPath = true }) }, "&:hover": { - backgroundColor: isMainPath ? "rgba(212, 212, 212, 0.7)" : "rgba(18, 109, 127, 0.3)", + backgroundColor: isMainPath ? theme.palette.mobileNavigation.main.chip.hover : theme.palette.mobileNavigation.sub.chip.hover, }, })); @@ -314,15 +311,15 @@ const Depth2Header = styled(Box)<{ isMainPath: boolean }>({ marginBottom: 10, }); -const Depth2Title = styled(Typography)<{ isMainPath: boolean }>(({ isMainPath }) => ({ - color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)", +const Depth2Title = styled(Typography)<{ isMainPath: boolean }>(({ theme, isMainPath }) => ({ + color: isMainPath ? theme.palette.mobileNavigation.main.text : theme.palette.mobileNavigation.sub.text, fontSize: 20, fontWeight: 800, })); -const Depth2Divider = styled(Box)<{ isMainPath: boolean }>(({ isMainPath }) => ({ +const Depth2Divider = styled(Box)<{ isMainPath: boolean }>(({ theme, isMainPath }) => ({ height: 1, - backgroundColor: isMainPath ? "rgba(255, 255, 255, 0.3)" : "rgba(18, 109, 127, 0.3)", + backgroundColor: isMainPath ? theme.palette.mobileNavigation.main.divider : theme.palette.mobileNavigation.sub.divider, marginBottom: 21, })); diff --git a/apps/pyconkr/src/styles/globalStyles.ts b/apps/pyconkr/src/styles/globalStyles.ts index 5cf92f5..d08c4f0 100644 --- a/apps/pyconkr/src/styles/globalStyles.ts +++ b/apps/pyconkr/src/styles/globalStyles.ts @@ -36,6 +36,47 @@ export const muiTheme = createTheme({ activeLanguage: "#126D7F", }, }, + mobileNavigation: { + main: { + background: + "linear-gradient(0deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)), linear-gradient(0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15))", + text: "#FFFFFF", + chip: { + background: "rgba(212, 212, 212, 0.5)", + hover: "rgba(212, 212, 212, 0.7)", + }, + divider: "rgba(255, 255, 255, 0.3)", + languageToggle: { + background: "transparent", + active: { + background: "rgba(255, 255, 255, 0.7)", + hover: "rgba(255, 255, 255, 0.8)", + }, + inactive: { + hover: "rgba(255, 255, 255, 0.1)", + }, + }, + }, + sub: { + background: "#B6D8D7", + text: "rgba(18, 109, 127, 0.9)", + chip: { + background: "rgba(18, 109, 127, 0.2)", + hover: "rgba(18, 109, 127, 0.3)", + }, + divider: "rgba(18, 109, 127, 0.3)", + languageToggle: { + background: "rgba(255, 255, 255, 0.1)", + active: { + background: "rgba(255, 255, 255, 0.9)", + hover: "rgba(255, 255, 255, 1)", + }, + inactive: { + hover: "rgba(255, 255, 255, 0.3)", + }, + }, + }, + }, text: { primary: "#000000", secondary: "#666666", diff --git a/types/emotion.d.ts b/types/emotion.d.ts index 6ad28f2..f1196ea 100644 --- a/types/emotion.d.ts +++ b/types/emotion.d.ts @@ -16,6 +16,46 @@ declare module "@mui/material/styles" { activeLanguage: string; }; }; + mobileNavigation: { + main: { + background: string; + text: string; + chip: { + background: string; + hover: string; + }; + divider: string; + languageToggle: { + background: string; + active: { + background: string; + hover: string; + }; + inactive: { + hover: string; + }; + }; + }; + sub: { + background: string; + text: string; + chip: { + background: string; + hover: string; + }; + divider: string; + languageToggle: { + background: string; + active: { + background: string; + hover: string; + }; + inactive: { + hover: string; + }; + }; + }; + }; } interface PaletteOptions { @@ -32,6 +72,46 @@ declare module "@mui/material/styles" { activeLanguage: string; }; }; + mobileNavigation?: { + main: { + background: string; + text: string; + chip: { + background: string; + hover: string; + }; + divider: string; + languageToggle: { + background: string; + active: { + background: string; + hover: string; + }; + inactive: { + hover: string; + }; + }; + }; + sub: { + background: string; + text: string; + chip: { + background: string; + hover: string; + }; + divider: string; + languageToggle: { + background: string; + active: { + background: string; + hover: string; + }; + inactive: { + hover: string; + }; + }; + }; + }; } interface PaletteColor { From e07e6367c2973c70ebc473cfbcf52047f17c4cd8 Mon Sep 17 00:00:00 2001 From: y00eunji Date: Wed, 18 Jun 2025 15:41:34 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=EB=B0=8F=20=EB=82=B4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/Header/Mobile/MobileHeader.tsx | 8 ++++---- .../components/layout/Header/Mobile/MobileNavigation.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx index 51b22a3..03b2f92 100644 --- a/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileHeader.tsx @@ -56,7 +56,7 @@ export const MobileHeader: React.FC = ({ isNavigationOpen = f }; const MobileHeaderContainer = styled("header")<{ isOpen: boolean; isMainPath: boolean }>(({ theme, isOpen, isMainPath }) => ({ - position: "fixed", + position: isMainPath ? "fixed" : "sticky", top: 0, left: 0, right: 0, @@ -71,11 +71,11 @@ const MobileHeaderContainer = styled("header")<{ isOpen: boolean; isMainPath: bo padding: "15px 23px", backgroundColor: isMainPath ? "rgba(182, 216, 215, 0.1)" : "#B6D8D7", - backdropFilter: "blur(8px)", - WebkitBackdropFilter: "blur(8px)", + backdropFilter: isMainPath ? "blur(8px)" : "none", + WebkitBackdropFilter: isMainPath ? "blur(8px)" : "none", color: isMainPath ? "white" : "rgba(18, 109, 127, 0.6)", - zIndex: theme.zIndex.appBar + 100000, + zIndex: isMainPath ? theme.zIndex.appBar + 100000 : theme.zIndex.appBar, })); const LeftContent = styled(Box)({ diff --git a/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx index 21ef26b..860f474 100644 --- a/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx +++ b/apps/pyconkr/src/components/layout/Header/Mobile/MobileNavigation.tsx @@ -192,7 +192,7 @@ export const MobileNavigation: React.FC = ({ isOpen, onCl - +