Skip to content

Commit

Permalink
Merge pull request #251 from boostcampwm-2024/Feature/#240_페이지_반응형_구현
Browse files Browse the repository at this point in the history
Feature/#240 페이지 반응형 구현
  • Loading branch information
github-actions[bot] authored Dec 2, 2024
2 parents c2eca98 + ceec4b2 commit caca289
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 53 deletions.
2 changes: 1 addition & 1 deletion client/src/components/button/IconButton.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cva } from "@styled-system/css";
export const iconButtonContainer = cva({
base: {
display: "flex",

flexShrink: 1,
justifyContent: "center",
alignItems: "center",
borderRadius: "xs",
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/button/textButton.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const textButton = cva({
base: {
borderRadius: "md",
width: "50%",
height: "40px",
paddingY: "4px",
cursor: "pointer",
},
variants: {
Expand Down
1 change: 1 addition & 0 deletions client/src/components/sidebar/Sidebar.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const plusIconBox = css({
position: "absolute",
bottom: "0px",
gap: "md",
flexShrink: 1,
justifyContent: "space-between",
alignItems: "center",
width: "100%",
Expand Down
10 changes: 10 additions & 0 deletions client/src/features/auth/AuthButton.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { css } from "@styled-system/css";

export const container = css({
display: "flex",
gap: "md",
flexGrow: 1,
justifyContent: "end",
alignItems: "center",
width: "auto",
});
9 changes: 5 additions & 4 deletions client/src/features/auth/AuthButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Modal } from "@components/modal/modal";
import { useModal } from "@components/modal/useModal";
import { useCheckLogin } from "@stores/useUserStore";
import { AuthModal } from "./AuthModal";
import { container } from "./AuthButton.style";

export const AuthButton = () => {
const isLogin = useCheckLogin();
Expand All @@ -23,13 +24,13 @@ export const AuthButton = () => {
const { mutate: logout } = useLogoutMutation(closeLogoutModal);

return (
<>
<div className={container}>
{isLogin ? (
<TextButton variant="secondary" onClick={openLogoutModal}>
<TextButton onClick={openLogoutModal} variant="secondary">
로그아웃
</TextButton>
) : (
<TextButton variant="secondary" onClick={openAuthModal}>
<TextButton onClick={openAuthModal} variant="secondary">
로그인
</TextButton>
)}
Expand All @@ -44,6 +45,6 @@ export const AuthButton = () => {
>
<p>로그아웃 하시겠습니까?</p>
</Modal>
</>
</div>
);
};
5 changes: 4 additions & 1 deletion client/src/features/page/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export const Page = ({
handleTitleChange,
serializedEditorData,
}: PageProps) => {
const { position, size, pageDrag, pageResize, pageMinimize, pageMaximize } = usePage({ x, y });
const { position, size, isMaximized, pageDrag, pageResize, pageMinimize, pageMaximize } = usePage(
{ x, y },
);
const [serializedEditorDatas, setSerializedEditorDatas] =
useState<serializedEditorDataProps | null>(serializedEditorData);

Expand Down Expand Up @@ -74,6 +76,7 @@ export const Page = ({
<div className={pageHeader} onPointerDown={pageDrag} onClick={handlePageClick}>
<PageTitle title={title} icon={icon} />
<PageControlButton
isMaximized={isMaximized}
onPageClose={() => handlePageClose(id)}
onPageMaximize={pageMaximize}
onPageMinimize={pageMinimize}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export const pageControlButton = cva({
width: "20px",
height: "20px",
cursor: "pointer",
"&:disabled": {
background: "gray.400",
opacity: 0.5,
cursor: "not-allowed",
},
},
variants: {
color: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ import MinusIcon from "@assets/icons/minus.svg?react";
import { pageControlContainer, pageControlButton, iconBox } from "./PageControlButton.style";

interface PageControlButtonProps {
isMaximized: boolean;
onPageMinimize?: () => void;
onPageMaximize?: () => void;
onPageClose?: () => void;
}

export const PageControlButton = ({
isMaximized,
onPageMinimize,
onPageMaximize,
onPageClose,
}: PageControlButtonProps) => {
return (
<div className={pageControlContainer}>
<button className={pageControlButton({ color: "yellow" })} onClick={onPageMinimize}>
<button
className={pageControlButton({ color: "yellow" })}
onClick={onPageMinimize}
disabled={isMaximized}
>
<MinusIcon className={iconBox} />
</button>
<button className={pageControlButton({ color: "green" })} onClick={onPageMaximize}>
Expand Down
148 changes: 118 additions & 30 deletions client/src/features/page/hooks/usePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,24 @@ export const DIRECTIONS = [

type Direction = (typeof DIRECTIONS)[number];

// 만약 maximize 상태면, 화면이 커질때도 꽉 촤게 해줘야함.
export const usePage = ({ x, y }: Position) => {
const [position, setPosition] = useState<Position>({ x, y });
const [size, setSize] = useState<Size>({
width: PAGE.WIDTH,
height: PAGE.HEIGHT,
});
const [prevPosition, setPrevPosition] = useState<Position>({ x, y });
const [prevSize, setPrevSize] = useState<Size>({
width: PAGE.WIDTH,
height: PAGE.HEIGHT,
});

const [isMaximized, setIsMaximized] = useState(false);
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 Down Expand Up @@ -210,19 +192,125 @@ export const usePage = ({ x, y }: Position) => {
};

const pageMaximize = () => {
setPosition({ x: 0, y: 0 });
setSize({
width: window.innerWidth - getSidebarWidth() - PADDING,
height: window.innerHeight - PADDING,
});
if (isMaximized) {
// 최대화가 된 상태에서 다시 최대화 버튼을 누르면, 원래 위치, 크기로 돌아가야함.
setPosition(prevPosition);
setSize(prevSize);
setIsMaximized(false);
} else {
// 최대화할시, 추후 이전 상태로 돌아가기 위해 prev 위치,크기 저장
setPrevPosition({ ...position });
setPrevSize({ ...size });
setPosition({ x: 0, y: 0 });
setSize({
width: window.innerWidth - getSidebarWidth() - PADDING,
height: window.innerHeight - PADDING,
});
setIsMaximized(true);
}
};

useEffect(() => {
if (isMaximized) {
setSize({
width: window.innerWidth - getSidebarWidth() - PADDING,
height: window.innerHeight - PADDING,
});
}
}, [isSidebarOpen]);

const adjustPageToWindow = () => {
const maxWidth = window.innerWidth - getSidebarWidth() - PADDING;
const maxHeight = window.innerHeight - PADDING;

let newWidth = Math.min(size.width, maxWidth);
let newHeight = Math.min(size.height, maxHeight);

// 최소 크기 보장
newWidth = Math.max(PAGE.MIN_WIDTH, newWidth);
newHeight = Math.max(PAGE.MIN_HEIGHT, newHeight);

// 새로운 위치 계산
let newX = position.x;
let newY = position.y;

// 오른쪽 경계를 벗어나는 경우
if (newX + newWidth > maxWidth) {
newX = Math.max(0, maxWidth - newWidth);
}

// 아래쪽 경계를 벗어나는 경우
if (newY + newHeight > maxHeight) {
newY = Math.max(0, maxHeight - newHeight);
}

// 크기나 위치가 변경된 경우에만 상태 업데이트
if (
newWidth !== size.width ||
newHeight !== size.height ||
newX !== position.x ||
newY !== position.y
) {
setSize({ width: newWidth, height: newHeight });
setPosition({ x: newX, y: newY });
}
};

// maximize 상태일 때의 resize 처리
useEffect(() => {
if (!isMaximized) return;

let timeoutId: NodeJS.Timeout;
const handleMaximizedResize = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
const newWidth = window.innerWidth - getSidebarWidth() - PADDING;
const newHeight = window.innerHeight - PADDING;

// 실제로 크기가 변경될 때만 update
if (size.width !== newWidth || size.height !== newHeight) {
setSize({ width: newWidth, height: newHeight });
}
}, 100);
};

window.addEventListener("resize", handleMaximizedResize);
handleMaximizedResize();

return () => {
window.removeEventListener("resize", handleMaximizedResize);
clearTimeout(timeoutId);
};
}, [isMaximized, isSidebarOpen]); // maximize 상태와 sidebar 상태만 의존성

// 일반 상태일 때의 resize 처리
useEffect(() => {
if (isMaximized) return;

let timeoutId: NodeJS.Timeout;
const handleNormalResize = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
adjustPageToWindow();
}, 100);
};

window.addEventListener("resize", handleNormalResize);
handleNormalResize();

return () => {
window.removeEventListener("resize", handleNormalResize);
clearTimeout(timeoutId);
};
}, [position, size, isSidebarOpen]);

return {
position,
size,
pageDrag,
pageResize,
pageMinimize,
pageMaximize,
isMaximized,
};
};
3 changes: 3 additions & 0 deletions client/src/features/workSpace/WorkSpace.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export const content = css({
export const workSpaceContainer = cva({
base: {
display: "flex",
width: "100vw",
height: "100vh",
overflow: "hidden",
transition: "opacity 0.3s ease-in-out",
},
variants: {
Expand Down
4 changes: 2 additions & 2 deletions client/src/features/workSpace/WorkSpace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const WorkSpace = () => {
closePage,
updatePage,
initPages,
initPagePosition,
// initPagePosition,
openPage,
} = usePagesManage(workspace, clientId);
const visiblePages = pages.filter((page) => page.isVisible && page.isLoaded);
Expand All @@ -42,7 +42,7 @@ export const WorkSpace = () => {
setWorkspace(newWorkspace);

initPages(newWorkspace.pageList);
initPagePosition();
// initPagePosition();
}
}, [workspaceMetadata]);

Expand Down
13 changes: 0 additions & 13 deletions client/src/features/workSpace/hooks/usePagesManage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,6 @@ export const usePagesManage = (workspace: WorkSpace | null, clientId: number | n
}
};

// 서버에서 처음 불러올때는 좌표를 모르기에, 초기화 과정 필요
const initPagePosition = () => {
setPages((prevPages) =>
prevPages.map((page, index) => ({
...page,
x: PAGE_OFFSET * index,
y: PAGE_OFFSET * index,
})),
);
};

const initPages = (list: CRDTPage[]) => {
const pageList: Page[] = list.map(
(crdtPage, index) =>
Expand All @@ -244,7 +233,6 @@ export const usePagesManage = (workspace: WorkSpace | null, clientId: number | n

useEffect(() => {
initPages([]);
initPagePosition();
}, []);

return {
Expand All @@ -256,6 +244,5 @@ export const usePagesManage = (workspace: WorkSpace | null, clientId: number | n
updatePageData,
updatePage,
initPages,
initPagePosition,
};
};
1 change: 1 addition & 0 deletions client/src/styles/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const globalStyles = defineGlobalStyles({
backgroundImage: 'url("./assets/images/background.png")',
backgroundSize: "cover",
fontFamily: "Pretendard, sans-serif",
boxSizing: "border-box",
},
// 스크롤바 전체
"::-webkit-scrollbar": {
Expand Down

0 comments on commit caca289

Please sign in to comment.