Skip to content

Commit

Permalink
Merge pull request #105 from boostcampwm-2024/Feature/#104_탭_브라우징_반응형_구현
Browse files Browse the repository at this point in the history
Feature/#104 탭 브라우징 반응형 구현
  • Loading branch information
github-actions[bot] authored Nov 13, 2024
2 parents 496c2bd + c0ef9b3 commit 38a012a
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 29 deletions.
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"framer-motion": "^11.11.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"socket.io-client": "^4.8.1"
"socket.io-client": "^4.8.1",
"zustand": "^5.0.1"
},
"devDependencies": {
"@pandabox/prettier-plugin": "^0.1.3",
Expand Down
29 changes: 29 additions & 0 deletions client/src/components/sidebar/Sidebar.animation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SIDE_BAR } from "@constants/size";

export const animation = {
initial: {
x: -100,
Expand All @@ -14,3 +16,30 @@ export const animation = {
duration: 0.5,
},
};

export const sidebarVariants = {
open: {
width: `${SIDE_BAR.WIDTH}px`,
transition: { duration: 0.2, ease: "easeOut" },
},
closed: {
width: `${SIDE_BAR.MIN_WIDTH}px`,
},
};

export const contentVariants = {
open: {
opacity: 1,
x: 0,
display: "flex",
transition: { delay: 0.2 },
},
closed: {
opacity: 0,
x: -20,
transitionEnd: {
delay: 0.2,
display: "none",
},
},
};
11 changes: 10 additions & 1 deletion client/src/components/sidebar/Sidebar.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const sidebarContainer = cx(
display: "flex",
gap: "lg",
flexDirection: "column",
width: "sidebar.width",
height: "calc(100vh - 40px)",
marginBlock: "20px",
}),
Expand All @@ -24,3 +23,13 @@ export const plusIconBox = css({
justifyContent: "start",
paddingInline: "md",
});

export const sidebarToggleButton = css({
zIndex: 10,
position: "absolute",
top: "4px",
right: "16px",
color: "gray.500",
fontSize: "24px",
cursor: "pointer",
});
57 changes: 34 additions & 23 deletions client/src/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { motion, AnimatePresence } from "framer-motion";
import { motion } from "framer-motion";
import { IconButton } from "@components/button/IconButton";
import { useIsSidebarOpen, useSidebarActions } from "src/stores/useSidebarStore";
import { Page } from "src/types/page";
import { MenuButton } from "./MenuButton";
import { PageItem } from "./PageItem";
import { animation } from "./Sidebar.animation";
import { sidebarContainer, navWrapper, plusIconBox } from "./Sidebar.style";
import { animation, contentVariants, sidebarVariants } from "./Sidebar.animation";
import { sidebarContainer, navWrapper, plusIconBox, sidebarToggleButton } from "./Sidebar.style";

export const Sidebar = ({
pages,
Expand All @@ -15,26 +16,36 @@ export const Sidebar = ({
handlePageAdd: () => void;
handlePageSelect: (pageId: number, isSidebar: boolean) => void;
}) => {
const isSidebarOpen = useIsSidebarOpen();
const { toggleSidebar } = useSidebarActions();

return (
<aside className={sidebarContainer}>
<MenuButton />
<AnimatePresence>
<nav className={navWrapper}>
{pages?.map((item) => (
<motion.div
key={item.id}
initial={animation.initial}
animate={animation.animate}
transition={animation.transition}
>
<PageItem {...item} onClick={() => handlePageSelect(item.id, true)} />
</motion.div>
))}
</nav>
</AnimatePresence>
<motion.div className={plusIconBox}>
<IconButton icon="➕" size="sm" onClick={handlePageAdd} />
</motion.div>
</aside>
<motion.aside
className={sidebarContainer}
initial="open"
animate={isSidebarOpen ? "open" : "closed"}
variants={sidebarVariants}
>
<div className={sidebarToggleButton} onClick={toggleSidebar}>
{isSidebarOpen ? "«" : "»"}
</div>

<motion.nav className={navWrapper} variants={contentVariants}>
<MenuButton />
{pages?.map((item) => (
<motion.div
key={item.id}
initial={animation.initial}
animate={animation.animate}
transition={animation.transition}
>
<PageItem {...item} onClick={() => handlePageSelect(item.id, true)} />
</motion.div>
))}
<div className={plusIconBox}>
<IconButton icon="➕" size="sm" onClick={handlePageAdd} />
</div>
</motion.nav>
</motion.aside>
);
};
1 change: 1 addition & 0 deletions client/src/constants/size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const PAGE = {
};

export const SIDE_BAR = {
MIN_WIDTH: 40,
WIDTH: 300,
};
38 changes: 34 additions & 4 deletions client/src/features/page/hooks/usePage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import { PAGE, SIDE_BAR } from "@constants/size";
import { SPACING } from "@constants/spacing";
import { useState } from "react";
import { useIsSidebarOpen } from "src/stores/useSidebarStore";
import { Position, Size } from "src/types/page";

const PADDING = SPACING.MEDIUM * 2;
Expand All @@ -12,6 +13,35 @@ export const usePage = ({ x, y }: Position) => {
height: PAGE.HEIGHT,
});

const isSidebarOpen = useIsSidebarOpen();

const getSidebarWidth = () => (isSidebarOpen ? SIDE_BAR.WIDTH : SIDE_BAR.MIN_WIDTH);

useEffect(() => {
// x 범위 넘어가면 x 위치 조정
const sidebarWidth = getSidebarWidth();
if (position.x > window.innerWidth - size.width - sidebarWidth - PADDING) {
// 만약 최대화 상태라면(사이드바 열었을때, 사이드바가 화면을 가린다면), 포지션 0으로 바꾸고 width도 재조정
// 만약 최대화가 아니라면, 포지션만 조정하고, 사이즈는 그대로
if (size.width > window.innerWidth - sidebarWidth - PADDING) {
setPosition({ x: 0, y: position.y });
setSize({
width: window.innerWidth - sidebarWidth - PADDING,
height: size.height,
});
} else {
setPosition({
x: position.x - sidebarWidth + PADDING,
y: position.y,
});
setSize({
width: size.width,
height: size.height,
});
}
}
}, [isSidebarOpen]);

const pageDrag = (e: React.PointerEvent) => {
e.preventDefault();
const startX = e.clientX - position.x;
Expand All @@ -20,7 +50,7 @@ export const usePage = ({ x, y }: Position) => {
const handleDragMove = (e: PointerEvent) => {
const newX = Math.max(
0,
Math.min(window.innerWidth - size.width - SIDE_BAR.WIDTH - PADDING, e.clientX - startX),
Math.min(window.innerWidth - size.width - getSidebarWidth() - PADDING, e.clientX - startX),
);
const newY = Math.max(
0,
Expand Down Expand Up @@ -51,7 +81,7 @@ export const usePage = ({ x, y }: Position) => {

const newWidth = Math.max(
PAGE.MIN_WIDTH,
Math.min(startWidth + deltaX, window.innerWidth - position.x - SIDE_BAR.WIDTH - PADDING),
Math.min(startWidth + deltaX, window.innerWidth - position.x - getSidebarWidth() - PADDING),
);

const newHeight = Math.max(
Expand Down Expand Up @@ -81,7 +111,7 @@ export const usePage = ({ x, y }: Position) => {
const pageMaximize = () => {
setPosition({ x: 0, y: 0 });
setSize({
width: window.innerWidth - SIDE_BAR.WIDTH - PADDING,
width: window.innerWidth - getSidebarWidth() - PADDING,
height: window.innerHeight - PADDING,
});
};
Expand Down
18 changes: 18 additions & 0 deletions client/src/stores/useSidebarStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { create } from "zustand";

interface SidebarStore {
isSidebarOpen: boolean;
actions: {
toggleSidebar: () => void;
};
}

const useSidebarStore = create<SidebarStore>((set) => ({
isSidebarOpen: true,
actions: {
toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
},
}));

export const useIsSidebarOpen = () => useSidebarStore((state) => state.isSidebarOpen);
export const useSidebarActions = () => useSidebarStore((state) => state.actions);
26 changes: 26 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 38a012a

Please sign in to comment.