Skip to content

Commit

Permalink
Merge pull request #21 from boostcampwm-2024/refactor/responsive-web
Browse files Browse the repository at this point in the history
♻️ refactor: 채팅, 헤더 반응형 작업
  • Loading branch information
jungmyunggi authored Jan 20, 2025
2 parents 0f2719e + e6103e7 commit 2c1ee31
Show file tree
Hide file tree
Showing 14 changed files with 318 additions and 269 deletions.
27 changes: 24 additions & 3 deletions client/src/components/chat/ChatButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,27 @@ import { MessageCircleMore, X } from "lucide-react";

import { useSidebar } from "@/components/ui/sidebar";

import { useSidebarStore } from "@/store/useSidebarStore";

export default function ChatButton() {
return <div className="flex items-center gap-2"></div>;
}
export function OpenChat() {
const { toggleSidebar } = useSidebar();
const { toggleSidebar, isMobile, setOpenMobile } = useSidebar();
if (isMobile) {
return (
<>
<button
onClick={() => {
setOpenMobile(true);
}}
className="w-full"
>
채팅
</button>
</>
);
}

return (
<button
Expand All @@ -19,9 +35,14 @@ export function OpenChat() {
}
export function CloseChat() {
const { toggleSidebar } = useSidebar();

const { isOpen, setIsOpen } = useSidebarStore();
return (
<button onClick={toggleSidebar}>
<button
onClick={() => {
toggleSidebar();
if (isOpen) setIsOpen();
}}
>
<X size={16} />
</button>
);
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/chat/layout/ChatFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function ChatFooter() {
useKeyboardShortcut("Enter", () => handleSendMessage(), false);

return (
<SheetFooter className="flex items-center p-2">
<SheetFooter className="flex flex-row items-center p-2">
<Input
placeholder="메시지를 입력하세요"
value={message}
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/chat/layout/ChatSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const RenderHistory = ({ chatHistory, isFull }: { chatHistory: ChatType[]; isFul
<span className="flex flex-col gap-3 px-3">
{chatHistory.map((item, index) => {
const isSameUser = index > 0 && chatHistory[index - 1]?.username === item.username;
return <ChatItem key={item.timestamp} chatItem={item} isSameUser={isSameUser} />;
return <ChatItem key={index} chatItem={item} isSameUser={isSameUser} />;
})}
</span>
);
Expand Down
112 changes: 8 additions & 104 deletions client/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";

import { AnimatePresence } from "framer-motion";
import { Menu } from "lucide-react";

import RssRegistrationModal from "@/components/RssRegistration/RssRegistrationModal";
import SideButton from "@/components/layout/SideButton";
import SideBar from "@/components/layout/Sidebar";
import SearchButton from "@/components/search/SearchButton";
import DesktopNavigation from "@/components/layout/navigation/DesktopNavigation";
import MobileNavigation from "@/components/layout/navigation/MobileNavigation";
import SearchModal from "@/components/search/SearchModal";
import { Button } from "@/components/ui/button";
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuList,
NavigationMenuLink,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";

import { useCustomToast } from "@/hooks/common/useCustomToast.ts";
import { useKeyboardShortcut } from "@/hooks/common/useKeyboardShortcut";

import logo from "@/assets/logo-denamu-main.svg";
import { useMediaQuery } from "@/hooks/common/useMediaQuery";

import { TOAST_MESSAGES } from "@/constants/messages";

export default function Header() {
const [modals, setModals] = useState({ search: false, rss: false, login: false, chat: false });
const { toast } = useCustomToast();
const isMobile = useMediaQuery("(max-width: 767px)");
const toggleModal = (modalType: "search" | "rss" | "login" | "chat") => {
if (modalType === "login") {
toast(TOAST_MESSAGES.SERVICE_NOT_PREPARED);
Expand All @@ -40,92 +26,10 @@ export default function Header() {

return (
<div className="border-b border-primary/20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="h-20 items-center overflow-hidden flex justify-between relative z-50">
{/* 로고 영역 */}
<button className="flex-shrink-0 relative z-50" onClick={() => location.reload()}>
<img className="h-14 w-auto cursor-pointer" src={logo} alt="Logo" />
</button>

{/* 중앙 검색 버튼 */}
<div className="absolute left-1/2 transform -translate-x-1/2 w-full flex justify-center z-40">
<SearchButton handleSearchModal={() => toggleModal("search")} />
</div>

{/* 내비게이션 */}
<div className="flex-shrink-0 z-50">
<DesktopNavigation toggleModal={toggleModal} />
<MobileNavigation toggleModal={toggleModal} />
</div>
</div>
</div>
<AnimatePresence>
{modals.rss && <RssRegistrationModal onClose={() => toggleModal("rss")} rssOpen={modals.rss} />}
{modals.search && <SearchModal onClose={() => toggleModal("search")} />}
</AnimatePresence>
</div>
);
}

function DesktopNavigation({ toggleModal }: { toggleModal: (modalType: "search" | "rss" | "login") => void }) {
const navigate = useNavigate();

return (
<div className="hidden md:flex md:items-center">
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<SideButton />
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
className={`${navigationMenuTriggerStyle()} hover:text-primary hover:bg-primary/10`}
onClick={() => navigate("/about")}
href="#"
>
서비스 소개
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
className={`${navigationMenuTriggerStyle()} hover:text-primary hover:bg-primary/10`}
onClick={() => toggleModal("login")}
href="#"
>
로그인
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<Button variant="default" onClick={() => toggleModal("rss")} className="bg-primary hover:bg-primary/90">
블로그 등록
</Button>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
);
}

function MobileNavigation({ toggleModal }: { toggleModal: (modalType: "search" | "rss" | "login") => void }) {
return (
<div className="md:hidden">
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" size="icon" className="hover:border-primary hover:text-primary">
<Menu className="h-4 w-4" />
</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle className="text-primary">메뉴</SheetTitle>
</SheetHeader>
<SideBar
handleRssModal={() => toggleModal("rss")}
handleSearchModal={() => toggleModal("search")}
handleLoginModal={() => toggleModal("login")}
/>
</SheetContent>
</Sheet>
{isMobile ? <MobileNavigation toggleModal={toggleModal} /> : <DesktopNavigation toggleModal={toggleModal} />}
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"></div>
{modals.rss && <RssRegistrationModal onClose={() => toggleModal("rss")} rssOpen={modals.rss} />}
{modals.search && <SearchModal onClose={() => toggleModal("search")} />}
</div>
);
}
41 changes: 33 additions & 8 deletions client/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,49 @@
import { Search } from "lucide-react";
import { useNavigate } from "react-router-dom";

import { Button } from "@/components/ui/button";

import { Chat } from "../chat/Chat";
import { OpenChat } from "../chat/ChatButton";
import { SidebarProvider } from "../ui/sidebar";
import { useTapStore } from "@/store/useTapStore";

type SideBarType = {
handleRssModal: () => void;
handleSearchModal: () => void;
handleLoginModal: () => void;
handleSidebar: () => void;
};

export default function SideBar({ handleRssModal, handleSearchModal, handleLoginModal }: SideBarType) {
export default function SideBar({ handleRssModal, handleLoginModal, handleSidebar }: SideBarType) {
const navigate = useNavigate();
const { tap, setTap } = useTapStore();
const actionAndClose = (fn: () => void) => {
fn();
handleSidebar();
};
return (
<div className="flex flex-col gap-4 p-4">
<Button onClick={handleSearchModal} variant="outline">
<Search />
검색
<Button onClick={() => navigate("/about")} variant="outline">
서비스 소개
</Button>
<Button variant="outline" className="w-full" onClick={handleLoginModal}>
<Button variant="outline" className="w-full" onClick={() => actionAndClose(handleLoginModal)}>
로그인
</Button>
<Button variant="outline" className="w-full" onClick={handleRssModal}>
{tap === "main" ? (
<Button variant="outline" onClick={() => actionAndClose(() => setTap("chart"))}>
차트
</Button>
) : (
<Button variant="outline" onClick={() => actionAndClose(() => setTap("main"))}>
</Button>
)}
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-[40px] overflow-hidden inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0">
<SidebarProvider>
<Chat />
<OpenChat />
</SidebarProvider>
</div>
<Button variant="default" className="w-full bg-primary" onClick={() => actionAndClose(handleRssModal)}>
블로그 등록
</Button>
</div>
Expand Down
69 changes: 69 additions & 0 deletions client/src/components/layout/navigation/DesktopNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useNavigate } from "react-router-dom";

import SideButton from "@/components/layout/SideButton";
import SearchButton from "@/components/search/SearchButton";
import { Button } from "@/components/ui/button";
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuList,
NavigationMenuLink,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";

import logo from "@/assets/logo-denamu-main.svg";

export default function DesktopNavigation({
toggleModal,
}: {
toggleModal: (modalType: "search" | "rss" | "login") => void;
}) {
const navigate = useNavigate();

return (
<div className="h-20 items-center flex justify-between relative px-[20px]">
{/* 로고 */}
<button className="flex-shrink-0 relative z-50" onClick={() => location.reload()}>
<img className="h-14 w-auto cursor-pointer" src={logo} alt="Logo" />
</button>

{/* 검색 */}
<div className="absolute left-1/2 transform -translate-x-1/2 w-[25%] flex justify-center z-40">
<SearchButton handleSearchModal={() => toggleModal("search")} />
</div>
{/* 버튼 */}
<div className="flex items-center z-50">
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<SideButton />
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
className={`${navigationMenuTriggerStyle()} hover:text-primary hover:bg-primary/10`}
onClick={() => navigate("/about")}
href="#"
>
서비스 소개
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
className={`${navigationMenuTriggerStyle()} hover:text-primary hover:bg-primary/10`}
onClick={() => toggleModal("login")}
href="#"
>
로그인
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<Button variant="default" onClick={() => toggleModal("rss")} className="bg-primary hover:bg-primary/90">
블로그 등록
</Button>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
</div>
);
}
53 changes: 53 additions & 0 deletions client/src/components/layout/navigation/MobileNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Menu, X } from "lucide-react";

import SideBar from "@/components/layout/Sidebar";
import SearchButton from "@/components/search/SearchButton";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";

import logo from "@/assets/logo-denamu-title.svg";

import { useSidebarStore } from "@/store/useSidebarStore";

export default function MobileNavigation({
toggleModal,
}: {
toggleModal: (modalType: "search" | "rss" | "login") => void;
}) {
const { isOpen, setIsOpen } = useSidebarStore();
return (
<div className="h-20 items-center flex justify-between relative px-[10px]">
{/* 로고 */}
<button className="flex-shrink-0 relative z-50" onClick={() => location.reload()}>
<img className="h-14 w-auto cursor-pointer" src={logo} alt="Logo" />
</button>

{/* 검색 */}
<div className="absolute left-1/2 transform -translate-x-1/2 w-[50%] flex justify-center z-40">
<SearchButton handleSearchModal={() => toggleModal("search")} />
</div>
{/* 햄버거 버튼 */}
<Sheet open={isOpen}>
<Button variant="outline" size="icon" className="hover:border-primary hover:text-primary" onClick={setIsOpen}>
<Menu className="h-4 w-4" />
</Button>
<SheetContent className="w-full">
<SheetHeader>
<SheetTitle className="text-primary">메뉴</SheetTitle>
<Button
className="absolute right-2 top-0 rounded-sm opacity-70 bg-transparent text-black active:bg-transparent"
onClick={setIsOpen}
>
<X />
</Button>
</SheetHeader>
<SideBar
handleRssModal={() => toggleModal("rss")}
handleLoginModal={() => toggleModal("login")}
handleSidebar={setIsOpen}
/>
</SheetContent>
</Sheet>
</div>
);
}
2 changes: 1 addition & 1 deletion client/src/components/search/SearchButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function SearchButton({ handleSearchModal }: { handleSearchModal:
items-center
hover:bg-primary/5
max-w-[400px]
hover:bg-seconary
"
onClick={handleSearchModal}
>
Expand Down
Loading

0 comments on commit 2c1ee31

Please sign in to comment.