diff --git a/package-lock.json b/package-lock.json index 34276a5..a3474f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,11 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", + "@types/lodash": "^4.14.186", "@types/node": "^16.11.59", "@types/react": "^18.0.20", "@types/react-dom": "^18.0.6", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.4.0", @@ -3985,6 +3987,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/lodash": { + "version": "4.14.186", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", + "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==" + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -19516,6 +19523,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "@types/lodash": { + "version": "4.14.186", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", + "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==" + }, "@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", diff --git a/package.json b/package.json index 9e7a710..164b901 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", + "@types/lodash": "^4.14.186", "@types/node": "^16.11.59", "@types/react": "^18.0.20", "@types/react-dom": "^18.0.6", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.4.0", diff --git a/src/assets/member.png b/src/assets/member.png new file mode 100644 index 0000000..90aa9f5 Binary files /dev/null and b/src/assets/member.png differ diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index 7dbd58a..23bb027 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -44,6 +44,8 @@ interface ButtonPorps { * @description Apply onClick event to the button */ onClick?: () => void; + hoverBgColor?: "purple" | "black" | "block"; + hoverColor?: "white" | "black"; children: React.ReactNode; } @@ -70,6 +72,8 @@ const Button = ({ radius, type, onClick, + hoverBgColor, + hoverColor, children, }: ButtonPorps) => { const Component = styled.button` @@ -93,6 +97,13 @@ const Button = ({ : theme.background[backgroundColor]}; cursor: pointer; border: none; + transition: all 0.2s ease-in-out; + &:hover { + color: ${({ theme }) => + !hoverColor ? color : theme.font.color[hoverColor]}; + background-color: ${({ theme }) => + !hoverBgColor ? backgroundColor : theme.background[hoverBgColor]}; + } `; return ( diff --git a/src/components/common/TextField.tsx b/src/components/common/TextField.tsx index 3ead37f..acadfcc 100644 --- a/src/components/common/TextField.tsx +++ b/src/components/common/TextField.tsx @@ -6,42 +6,34 @@ interface TextFieldProps { * @default 'inline' */ display?: "block" | "inline" | "inline-block"; - /** * @default 'auto' */ width?: string; - /** * @default 'auto' */ height?: string; - /** * @default '0' */ margin?: string; - /** * @default 'standard' */ variant?: "standard" | "outlined"; - /** * @default 'text' */ type?: "text" | "password" | "email" | "url"; - disabled?: boolean; error?: boolean; placeholder?: string; helperText?: string; // TODO: Add helper text - /** * @default 'large' */ size?: "small" | "large"; - value?: string; onChange: (event: React.ChangeEvent) => void; children?: React.ReactNode; @@ -86,6 +78,7 @@ function TextField(props: TextFieldProps) { disabled={props.disabled} placeholder={props.placeholder} value={props.value} + onChange={props.onChange} /> {props.helperText} diff --git a/src/hooks/test.tsx b/src/components/mainpage/slideSection/index.tsx similarity index 100% rename from src/hooks/test.tsx rename to src/components/mainpage/slideSection/index.tsx diff --git a/src/components/mainpage/videoTextSection/FirstScrollText.tsx b/src/components/mainpage/videoTextSection/FirstScrollText.tsx new file mode 100644 index 0000000..af32024 --- /dev/null +++ b/src/components/mainpage/videoTextSection/FirstScrollText.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { Text } from "../../common"; +import { FirstTextBox } from "../../../styles/mainpage/videoTextSection"; +import { ScrollYType } from "."; +const FirstScrollText = ({ scrollY }: ScrollYType) => { + return ( + + + 좋은 사람들이 모여
+ 좋은 개발자가 되는 곳, +
+
+ ); +}; + +export default FirstScrollText; diff --git a/src/components/mainpage/videoTextSection/SecondScrollText.tsx b/src/components/mainpage/videoTextSection/SecondScrollText.tsx new file mode 100644 index 0000000..5e15d35 --- /dev/null +++ b/src/components/mainpage/videoTextSection/SecondScrollText.tsx @@ -0,0 +1,12 @@ +import { Text } from "../../common"; +import { SecondTextBox } from "../../../styles/mainpage/videoTextSection"; +import { ScrollYType } from "."; +const SecondScrollText = ({ scrollY }: ScrollYType) => { + return ( + +
NL
+
+ ); +}; + +export default SecondScrollText; diff --git a/src/components/mainpage/videoTextSection/ThirdScrollText.tsx b/src/components/mainpage/videoTextSection/ThirdScrollText.tsx new file mode 100644 index 0000000..414b2a1 --- /dev/null +++ b/src/components/mainpage/videoTextSection/ThirdScrollText.tsx @@ -0,0 +1,56 @@ +// import { Text } from "../../common"; +// import { ThirdTextBox } from "../../../styles/mainpage/videoTextSection"; +// import { ScrollYType } from "."; +// const ThirdScrollText = ({ scrollY }: ScrollYType) => { +// return ( +// +// +// 잘하는 개발자가 되기 위한 방법은 많습니다. +//
+// 하지만 우리들은 좋은 개발자가 되고 싶습니다. +//
+// +// 때로는 함께 목표를 위해 애써 달려가다가도,
+// 동료들을 위해 기다려 주고 도와줄 수 있는 그런 사람. +//
+// +// 목표를 달성하고 성취감을 느끼며 행복을 얻는 사람,
+// 그 행복을 느끼기 위해 더 노력하고,
이 노력으로 동료들의 수고를 +// 덜어주는 그런사람. +//
+//
+// ); +// }; + +// export default ThirdScrollText; +import { Text } from "../../common"; +import { ThirdTextBox } from "../../../styles/mainpage/videoTextSection"; +import { ScrollYType } from "."; +const ThirdScrollText = ({ scrollY }: ScrollYType) => { + return ( + +
+ + 잘하는 개발자가 되기 위한 방법은 많습니다. +
+ 하지만 우리들은 좋은 개발자가 되고 싶습니다. +
+
+
+ + 때로는 함께 목표를 위해 애써 달려가다가도,
+ 동료들을 위해 기다 려 주고 도와줄 수 있는 그런 사람. +
+
+
+ + 목표를 달성하고 성취감을 느끼며 행복을 얻는 사람,
+ 그 행복을 느끼기 위해 더 노력하고,
이 노력으로 동료들의 수고를 + 덜어주는 그런사람. +
+
+
+ ); +}; + +export default ThirdScrollText; diff --git a/src/components/mainpage/videoTextSection/index.tsx b/src/components/mainpage/videoTextSection/index.tsx new file mode 100644 index 0000000..d278e37 --- /dev/null +++ b/src/components/mainpage/videoTextSection/index.tsx @@ -0,0 +1,30 @@ +import { + VideoContainer, + VideoBox, + VideoIframe, +} from "../../../styles/mainpage/videoTextSection"; +import FirstScrollText from "./FirstScrollText"; +import SecondScrollText from "./SecondScrollText"; +import { useScrollY } from "../../../hooks/useScrollY"; +import ThirdScrollText from "./ThirdScrollText"; + +export interface ScrollYType { + scrollY: number; +} + +const VideoTextSection = () => { + const scrollY = useScrollY(); + const src = `https://www.youtube.com/embed/ttc8Q39E0zI?&autoplay=1&showinfo=0&mute=1&loop=1&playlist=ttc8Q39E0zI&controls=0&modestbranding=1&disablekb=1&iv_load_policy=3&showinfo=1&start=30`; + return ( + + + + + + + + + ); +}; + +export default VideoTextSection; diff --git a/src/components/member/Image.tsx b/src/components/member/Image.tsx new file mode 100644 index 0000000..955e2d1 --- /dev/null +++ b/src/components/member/Image.tsx @@ -0,0 +1,24 @@ +import styled from "@emotion/styled"; +import member from "../../assets/member.png"; + +const ImageContainer = styled.div` + width: calc(100% - 590px); + height: 100%; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + .img { + width: 100%; + } +`; + +const Image = () => { + return ( + + sign in + + ); +}; + +export default Image; diff --git a/src/components/member/signIn/Form.tsx b/src/components/member/signIn/Form.tsx new file mode 100644 index 0000000..c96a7b5 --- /dev/null +++ b/src/components/member/signIn/Form.tsx @@ -0,0 +1,58 @@ +import { Link } from "react-router-dom"; +import { Formation, FormContainer, Header } from "../../../styles/member/form"; +import { Button, Text, TextField } from "../../common"; + +const Form = () => { + return ( + +
+ + Log In + +
+ + { + console.log(22); + }} + /> + { + console.log(22); + }} + /> + + +
+ Don't have an account? + + + Sign Up + + +
+
+ ); +}; + +export default Form; diff --git a/src/components/member/signIn/index.tsx b/src/components/member/signIn/index.tsx new file mode 100644 index 0000000..5c9278d --- /dev/null +++ b/src/components/member/signIn/index.tsx @@ -0,0 +1,7 @@ +import Form from "./Form"; + +const SignIn = () => { + return
; +}; + +export default SignIn; diff --git a/src/components/member/signUp/Form.tsx b/src/components/member/signUp/Form.tsx new file mode 100644 index 0000000..41ad472 --- /dev/null +++ b/src/components/member/signUp/Form.tsx @@ -0,0 +1,96 @@ +import { Link } from "react-router-dom"; +import { Formation, FormContainer, Header } from "../../../styles/member/form"; +import { Button, Text, TextField } from "../../common"; + +const Form = () => { + return ( + +
+ + Sign Up + +
+ + { + console.log(22); + }} + /> + { + console.log(22); + }} + /> + { + console.log(22); + }} + /> + { + console.log(22); + }} + /> + { + console.log(22); + }} + /> + { + console.log(22); + }} + /> + + +
+ 이미 계정이 있으신가요? + + + Log In + + +
+
+ ); +}; + +export default Form; diff --git a/src/components/member/signUp/index.tsx b/src/components/member/signUp/index.tsx new file mode 100644 index 0000000..03616a8 --- /dev/null +++ b/src/components/member/signUp/index.tsx @@ -0,0 +1,7 @@ +import Form from "./Form"; + +const SignUp = () => { + return ; +}; + +export default SignUp; diff --git a/src/hooks/useScrollY.ts b/src/hooks/useScrollY.ts new file mode 100644 index 0000000..6f0f2d5 --- /dev/null +++ b/src/hooks/useScrollY.ts @@ -0,0 +1,18 @@ +import { useState, useMemo, useEffect } from "react"; +import { throttle } from "lodash"; + +export const useScrollY = () => { + const [scrollY, setScrollY] = useState(0); + const throttleScroll = useMemo( + () => + throttle(() => { + setScrollY(window.scrollY); + }, 0), + [] + ); + useEffect(() => { + window.addEventListener("scroll", throttleScroll); + }, [throttleScroll]); + + return scrollY; +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c8958ae..fb2c594 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,7 +1,12 @@ import React from "react"; +import VideoTextSection from "../components/mainpage/videoTextSection"; const MainPage = () => { - return
; + return ( +
+ +
+ ); }; export default MainPage; diff --git a/src/pages/member.tsx b/src/pages/member.tsx new file mode 100644 index 0000000..35f0e0f --- /dev/null +++ b/src/pages/member.tsx @@ -0,0 +1,20 @@ +import styled from "@emotion/styled"; +import ImageContainer from "../components/member/Image"; +import { Outlet } from "react-router-dom"; + +const SignInContainer = styled.div` + width: 100vw; + height: 100vh; + display: flex; +`; + +const Member = () => { + return ( + + + + + ); +}; + +export default Member; diff --git a/src/router.tsx b/src/router.tsx index af7e9f6..1a31314 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,13 +1,21 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; import Layout from "./components/layout"; import MainPage from "./pages"; +import SignIn from "./components/member/signIn"; +import SignUp from "./components/member/signUp"; +import Member from "./pages/member"; + function Router() { return ( - }> - }> + }> + } /> + + }> + } /> + } /> diff --git a/src/styles/mainpage/videoTextSection.ts b/src/styles/mainpage/videoTextSection.ts new file mode 100644 index 0000000..ec4e188 --- /dev/null +++ b/src/styles/mainpage/videoTextSection.ts @@ -0,0 +1,66 @@ +import styled from "@emotion/styled"; + +export const VideoContainer = styled.section` + width: 100vw; + height: 6000px; + margin-top: 105px; + position: relative; +`; + +export const VideoBox = styled.div<{ scrollY: number }>` + width: 100vw; + height: calc(100vh - 105px); + display: flex; + align-items: center; + justify-content: center; + position: fixed; + overflow: hidden; + opacity: ${(props) => props.scrollY > 6000 && 0}; + transition: 0.6s; +`; +export const VideoIframe = styled.iframe` + width: 100%; + height: max(100%, calc(100vw / 16 * 9)); +`; + +export const FirstTextBox = styled.div<{ scrollY: number }>` + position: fixed; + opacity: ${(props) => props.scrollY > 2000 && 0}; + transition: 0.8s; + text-align: center; + transform: ${(props) => props.scrollY > 1000 && "translateY(-80px)"}; +`; + +export const SecondTextBox = styled.div<{ scrollY: number }>` + position: fixed; + opacity: 0; + margin-top: 230px; + opacity: ${(props) => props.scrollY > 1000 && props.scrollY < 2000 && 1}; + transition: 0.8s; + text-align: center; +`; +export const ThirdTextBox = styled.div<{ scrollY: number }>` + position: fixed; + text-align: center; + display: flex; + flex-direction: column; + height: 65%; + .text1 { + opacity: 0; + opacity: ${(props) => props.scrollY > 3000 && props.scrollY < 5400 && 1}; + transition: 0.6s; + flex-grow: 1; + } + .text2 { + opacity: 0; + opacity: ${(props) => props.scrollY > 3900 && props.scrollY < 5400 && 1}; + transition: 0.6s; + flex-grow: 1; + } + .text3 { + opacity: 0; + opacity: ${(props) => props.scrollY > 4800 && props.scrollY < 5400 && 1}; + transition: 0.6s; + flex-grow: 1; + } +`; diff --git a/src/styles/member/form.ts b/src/styles/member/form.ts new file mode 100644 index 0000000..736d12d --- /dev/null +++ b/src/styles/member/form.ts @@ -0,0 +1,41 @@ +import styled from "@emotion/styled"; + +const FormContainer = styled.div` + width: 590px; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 120px; + background-color: white; + .sign-up { + width: 210px; + display: flex; + justify-content: space-between; + margin-top: 20px; + } + .sign-in { + width: 190px; + display: flex; + justify-content: space-between; + margin-top: 20px; + } +`; + +const Header = styled.div` + width: 100%; + height: 70px; + display: flex; + align-items: center; + margin-bottom: 20px; +`; + +const Formation = styled.form` + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +`; + +export { FormContainer, Header, Formation }; diff --git a/src/theme/theme.ts b/src/theme/theme.ts index 5df52b7..a151784 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -3,7 +3,7 @@ import { Theme } from "@emotion/react"; const theme: Theme = { font: { size: { - h1: 72, + h1: 64, h2: 36, h3: 32, h4: 24, @@ -32,7 +32,7 @@ const theme: Theme = { background: { white: "#FFFFFF", black: "#000000", - block: "#868686", + block: "#E7E7E7", activity: "#F0F0F8", yellow: "#FFDF6B", orange: "#FFA96B", diff --git a/tsconfig.json b/tsconfig.json index a273b0c..74caf0d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,4 +23,4 @@ "include": [ "src" ] -} +} \ No newline at end of file