diff --git a/package-lock.json b/package-lock.json index 8a5ccf7..48a4df9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-responsive": "^10.0.0", "react-router": "^7.1.5", "styled-components": "^6.1.15", "zustand": "^5.0.3" @@ -1953,6 +1954,12 @@ "node": ">=4" } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", + "license": "BSD" + }, "node_modules/css-to-react-native": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", @@ -2479,6 +2486,12 @@ "node": ">=8" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2691,6 +2704,15 @@ "yallist": "^3.0.2" } }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "license": "MIT", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2788,6 +2810,15 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2935,6 +2966,17 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -2997,6 +3039,12 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -3007,6 +3055,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.0.tgz", + "integrity": "sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-router": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz", @@ -3140,6 +3206,12 @@ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "license": "MIT" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", diff --git a/package.json b/package.json index 6ee0811..640eb7e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-responsive": "^10.0.0", "react-router": "^7.1.5", "styled-components": "^6.1.15", "zustand": "^5.0.3" diff --git a/src/hooks/.gitkeep b/src/hooks/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/hooks/useMediaQueries.ts b/src/hooks/useMediaQueries.ts new file mode 100644 index 0000000..646036b --- /dev/null +++ b/src/hooks/useMediaQueries.ts @@ -0,0 +1,20 @@ +import { useMediaQuery } from "react-responsive"; + +const useMediaQueries = () => { + /* + * Union 작업중 콘텐츠 박스에서 767보다 더 작은 크기 조절이 필요해서 isTiny를 추가했습니다. + * 반응형 작업하실 때 한번더 봐주시고 고쳐주시면 좋을거 같습니다. + */ + const isTiny = useMediaQuery({ query: "(max-width: 385px)" }) + const isMobile = useMediaQuery({ query: "(max-width: 767px)" }); + const isTablet = useMediaQuery({ + query: "(min-width: 768px) and (max-width: 1023px)", + }); + const isDesktop = useMediaQuery({ + query: "(min-width: 1024px)" + }); + + return { isTiny, isMobile, isTablet, isDesktop }; +}; + +export default useMediaQueries; \ No newline at end of file diff --git a/src/pages/Main/Union/ContentBox.styled.ts b/src/pages/Main/Union/ContentBox.styled.ts new file mode 100644 index 0000000..421377d --- /dev/null +++ b/src/pages/Main/Union/ContentBox.styled.ts @@ -0,0 +1,173 @@ +import styled from "styled-components"; + +export const Container = styled.div<{ $isMobile: boolean }>` + width: 100%; + height: ${(props) => (props.$isMobile ? "auto" : "500px")}; + + display: flex; + justify-content: center; + align-items: center; + + position: relative; + padding: ${(props) => (props.$isMobile ? "20px 0" : "0")}; +`; + +// 테두리에 Gradient가 안돼서 박스 두개를 겹쳐서 테두리 Gradient를 만들었습니다. +export const ContentBoxBorder = styled.div<{ $isMobile: boolean }>` + position: ${(props) => (props.$isMobile ? "relative" : "absolute")}; + top: 0; + width: ${(props) => (props.$isMobile ? "90%" : "700px")}; + height: ${(props) => (props.$isMobile ? "auto" : "500px")}; + + background: linear-gradient( + 90deg, + var(--FarmSystem_Orange) 0%, + var(--FarmSystem_Green02) 100% + ); + padding: 5px; + border-radius: 20px; + + display: flex; + justify-content: center; + align-items: center; + z-index: 10; + margin: 0 auto; +`; + +export const Content = styled.div` + width: 100%; + height: 100%; + + background-color: var(--FarmSystem_White); + border-radius: 15px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +export const ContentInfoTextBox = styled.div<{ $isMobile: boolean }>` + width: ${(props) => (props.$isMobile ? "90%" : "600px")}; + height: ${(props) => (props.$isMobile ? "auto" : "210px")}; + + color: var(--FarmSystem_Black); + font-style: normal; + font-weight: 500; + font-size: ${(props) => (props.$isMobile ? "20px" : "24px")}; + line-height: ${(props) => (props.$isMobile ? "30px" : "35px")}; + + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + + margin-bottom: 35px; + padding-top: ${(props) => (props.$isMobile ? "20px" : "0")}; + padding-bottom: ${(props) => (props.$isMobile ? "20px" : "0")}; +`; + +export const HighlightOrange = styled.span` + font-weight: 700; + color: var(--FarmSystem_Orange); +`; + +export const ActivityTitle = styled.h3<{ $isMobile: boolean }>` + width: ${(props) => (props.$isMobile ? "auto" : "149px")}; + height: ${(props) => (props.$isMobile ? "auto" : "38px")}; + + font-weight: 700; + font-size: ${(props) => (props.$isMobile ? "22px" : "24px")}; + line-height: ${(props) => (props.$isMobile ? "30px" : "35px")}; + color: var(--FarmSystem_Green01); + + display: flex; + align-items: center; + justify-content: center; + text-align: center; + + margin-bottom: ${(props) => (props.$isMobile ? "7px" : "17px")}; +`; + +export const ActivityList = styled.ul<{ $isMobile: boolean }>` + width: ${(props) => (props.$isMobile ? "90%" : "577px")}; + height: ${(props) => (props.$isMobile ? "auto" : "80px")}; + + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-end; + align-content: flex-start; + + gap: ${(props) => (props.$isMobile ? "1px 10px" : "10px 20px")}; + list-style-type: disc; + justify-content: ${(props) => (props.$isMobile ? "center" : "initial")}; +`; + +export const Li = styled.li<{ + $isMobile: boolean; + $isTiny?: boolean; +}>` + height: 35px; + + font-style: normal; + font-weight: 500; + font-size: ${(props) => + props.$isMobile + ? props.$isTiny + ? "18px" + : "20px" + : "24px"}; + line-height: ${(props) => (props.$isMobile ? "30px" : "35px")}; + color: var(--FarmSystem_Black); + + list-style-type: ${(props) => + props.$isMobile && props.$isTiny ? "none" : "disc"}; + list-style-position: ${(props) => + props.$isMobile && props.$isTiny ? "none" : "outside"}; + margin-left: ${(props) => + props.$isMobile + ? props.$isTiny + ? "0px" + : "20px" + : "40px"}; + text-align: center; +`; + +export const GradientContainer = styled.div<{ $isMobile: boolean }>` + position: absolute; + top: 0; + height: 500px; + // 배경 Gradient를 모바일에서도 보이게 하려면 이 부분 고쳐주시면 됩니다! + display: ${(props) => (props.$isMobile ? "none" : "flex")}; + gap: 80px; + justify-content: center; + align-items: center; +`; + +export const GradientLeft = styled.div<{ $isMobile: boolean }>` + width: ${(props) => (props.$isMobile ? "300px" : "560px")}; + height: ${(props) => (props.$isMobile ? "250px" : "400px")}; + + background: linear-gradient( + 270deg, + var(--FarmSystem_Orange) 50%, + var(--FarmSystem_White) 100% + ); + opacity: 0.5; + border-radius: 20px; +`; + +export const GradientRight = styled.div<{ $isMobile: boolean }>` + width: ${(props) => (props.$isMobile ? "300px" : "560px")}; + height: ${(props) => (props.$isMobile ? "250px" : "400px")}; + + background: linear-gradient( + 90deg, + var(--FarmSystem_Green02) 50%, + var(--FarmSystem_White) 100% + ); + opacity: 0.5; + border-radius: 20px; +`; diff --git a/src/pages/Main/Union/ContentBox.tsx b/src/pages/Main/Union/ContentBox.tsx new file mode 100644 index 0000000..46f801a --- /dev/null +++ b/src/pages/Main/Union/ContentBox.tsx @@ -0,0 +1,46 @@ +import * as S from "./ContentBox.styled"; +import useMediaQueries from "@/hooks/useMediaQueries"; + +export default function ContentBox() { + const { isTiny, isMobile } = useMediaQueries(); + + return ( + + + + + + + + +

+ Union은 +

+

SW/AI 기초 역량을 다지기 위한 과정으로,

+

트랙 구분 없이 다섯 가지 트랙의 다양한 주제에 대한

+

SW 기본 역량을 다집니다.

+

각 트랙의 멘토가 제공하는 프로젝트와 스터디에

+

멘티로서 참여하게 됩니다.

+
+ + 한 학기 활동 + + + + 월별 기술 블로그 + + + Farm System 아이디어톤 참가 + + + 스터디 정기 모임 + + + 트랙 멘토-멘티 프로그램 + + +
+
+
+ ); +} diff --git a/src/pages/Main/Union/Union.styled.ts b/src/pages/Main/Union/Union.styled.ts new file mode 100644 index 0000000..bdd9369 --- /dev/null +++ b/src/pages/Main/Union/Union.styled.ts @@ -0,0 +1,65 @@ +import styled from "styled-components"; + +export const Container = styled.section<{ $isMobile: boolean }>` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + height: ${(props) => (props.$isMobile ? "1000px" : "1500px")}; + user-select: none; + padding: ${(props) => (props.$isMobile ? "5px" : "0")}; +`; + +export const IntroduceText = styled.p<{ $isMobile: boolean }>` + width: ${(props) => (props.$isMobile ? "100%" : "500px")}; + height: ${(props) => (props.$isMobile ? "auto" : "86px")}; + + text-align: center; + color: var(--FarmSystem_Black); + font-style: normal; + font-weight: 500; + font-size: ${(props) => (props.$isMobile ? "24px" : "36px")}; + line-height: ${(props) => (props.$isMobile ? "32px" : "43px")}; + + margin-bottom: ${(props) => (props.$isMobile ? "50px" : "70px")}; +`; + +export const UnionTextContainer = styled.div<{ $isMobile: boolean }>` + display: grid; + grid-template-columns: ${(props) => + props.$isMobile ? "1fr 2fr 1fr" : "1fr 1fr 1fr 1fr"}; + justify-items: center; + align-items: start; + + width: ${(props) => (props.$isMobile ? "220px" : "300px")}; + margin-bottom: ${(props) => (props.$isMobile ? "30px" : "50px")}; +`; + +export const UnionText = styled.h2<{ $isMobile: boolean }>` + grid-column: 2 / 4; + text-align: center; + color: var(--FarmSystem_Orange); + font-size: ${(props) => (props.$isMobile ? "40px" : "48px")}; + font-weight: 700; +`; + +export const NewBadge = styled.div<{ $isMobile: boolean }>` + grid-column: 4 / 5; + width: ${(props) => (props.$isMobile ? "60px" : "70px")}; + height: ${(props) => (props.$isMobile ? "22px" : "25px")}; + background: var(--FarmSystem_Green02); + border-radius: 10px; + + font-style: normal; + font-weight: 500; + font-size: ${(props) => (props.$isMobile ? "14px" : "16px")}; + line-height: 19px; + color: var(--FarmSystem_White); + + display: flex; + align-items: center; + justify-content: center; + text-align: center; + margin-top: 10px; +`; diff --git a/src/pages/Main/Union/Union.tsx b/src/pages/Main/Union/Union.tsx index b67f0c2..755109e 100644 --- a/src/pages/Main/Union/Union.tsx +++ b/src/pages/Main/Union/Union.tsx @@ -1,7 +1,21 @@ +import ContentBox from "./ContentBox"; +import useMediaQueries from "@/hooks/useMediaQueries"; +import * as S from './Union.styled'; + export default function Union() { + const { isMobile } = useMediaQueries(); + return ( -
-

Union Section

-
+ + +

신입생이라서

+

어떤 트랙을 선택할지 고민되나요?

+
+ + Union + New + + +
); -} +};