diff --git a/package.json b/package.json index c70143cd..b90a5765 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@tanstack/react-query-devtools": "^5.17.12", "@vanilla-extract/integration": "^6.2.4", "@vanilla-extract/next-plugin": "^2.3.2", + "@yaireo/tagify": "^4.19.0", "axios": "^1.6.5", "next": "14.0.4", "react": "^18", @@ -37,6 +38,7 @@ "zustand": "^4.4.7" }, "devDependencies": { + "@svgr/webpack": "^8.1.0", "@commitlint/cli": "^18.6.0", "@commitlint/config-conventional": "^18.6.0", "@svgr/webpack": "^8.1.0", diff --git a/public/fonts/Pretendard-Black.woff2 b/public/fonts/Pretendard-Black.woff2 new file mode 100644 index 00000000..e409cc0a Binary files /dev/null and b/public/fonts/Pretendard-Black.woff2 differ diff --git a/public/fonts/Pretendard-Bold.woff2 b/public/fonts/Pretendard-Bold.woff2 new file mode 100644 index 00000000..8975b802 Binary files /dev/null and b/public/fonts/Pretendard-Bold.woff2 differ diff --git a/public/fonts/Pretendard-ExtraBold.woff2 b/public/fonts/Pretendard-ExtraBold.woff2 new file mode 100644 index 00000000..1a9caaf8 Binary files /dev/null and b/public/fonts/Pretendard-ExtraBold.woff2 differ diff --git a/public/fonts/Pretendard-ExtraLight.woff2 b/public/fonts/Pretendard-ExtraLight.woff2 new file mode 100644 index 00000000..a6bf1857 Binary files /dev/null and b/public/fonts/Pretendard-ExtraLight.woff2 differ diff --git a/public/fonts/Pretendard-Light.woff2 b/public/fonts/Pretendard-Light.woff2 new file mode 100644 index 00000000..a86436a3 Binary files /dev/null and b/public/fonts/Pretendard-Light.woff2 differ diff --git a/public/fonts/Pretendard-Medium.woff2 b/public/fonts/Pretendard-Medium.woff2 new file mode 100644 index 00000000..153fd556 Binary files /dev/null and b/public/fonts/Pretendard-Medium.woff2 differ diff --git a/public/fonts/Pretendard-Regular.woff2 b/public/fonts/Pretendard-Regular.woff2 new file mode 100644 index 00000000..ca8008ff Binary files /dev/null and b/public/fonts/Pretendard-Regular.woff2 differ diff --git a/public/fonts/Pretendard-SemiBold.woff2 b/public/fonts/Pretendard-SemiBold.woff2 new file mode 100644 index 00000000..79f80890 Binary files /dev/null and b/public/fonts/Pretendard-SemiBold.woff2 differ diff --git a/public/fonts/Pretendard-Thin.woff2 b/public/fonts/Pretendard-Thin.woff2 new file mode 100644 index 00000000..809bf22d Binary files /dev/null and b/public/fonts/Pretendard-Thin.woff2 differ diff --git a/public/fonts/init.ts b/public/fonts/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/public/fonts/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/public/icons/close_button.svg b/public/icons/close_button.svg new file mode 100644 index 00000000..80319e7d --- /dev/null +++ b/public/icons/close_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/default_profile.svg b/public/icons/default_profile.svg new file mode 100644 index 00000000..6322bad5 --- /dev/null +++ b/public/icons/default_profile.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/icons/search.svg b/public/icons/search.svg new file mode 100644 index 00000000..d244da79 --- /dev/null +++ b/public/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/x_circle_fill.svg b/public/icons/x_circle_fill.svg new file mode 100644 index 00000000..7cfdd245 --- /dev/null +++ b/public/icons/x_circle_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/init.ts b/public/images/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/public/images/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/public/images/mock_profile.png b/public/images/mock_profile.png new file mode 100644 index 00000000..a435695d Binary files /dev/null and b/public/images/mock_profile.png differ diff --git a/public/styles/init.ts b/public/styles/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/public/styles/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/src/app/(BeforeLogin)/_components/init.ts b/src/app/(BeforeLogin)/_components/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/src/app/(BeforeLogin)/_components/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/src/app/(BeforeLogin)/login/_components/init.ts b/src/app/(BeforeLogin)/login/_components/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/src/app/(BeforeLogin)/login/_components/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/src/app/(BeforeLogin)/login/page.tsx b/src/app/(BeforeLogin)/login/page.tsx deleted file mode 100644 index e0e5ebeb..00000000 --- a/src/app/(BeforeLogin)/login/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Login() { - return
로그인페이지
; -} diff --git a/src/app/create/CreateListMock.ts b/src/app/create/CreateListMock.ts new file mode 100644 index 00000000..a1a5f0dd --- /dev/null +++ b/src/app/create/CreateListMock.ts @@ -0,0 +1,71 @@ +interface UserProfileType { + id: number; + profileImageUrl: string; + nickname: string; +} + +const generateMockData = (count: number): UserProfileType[] => { + const mockData: UserProfileType[] = []; + + mockData.push({ + id: 101, + profileImageUrl: `/images/mock_profile.png`, + nickname: '안유진', + }); + + mockData.push({ + id: 102, + profileImageUrl: `/images/mock_profile.png`, + nickname: '강나현', + }); + + mockData.push({ + id: 103, + profileImageUrl: `/images/mock_profile.png`, + nickname: '민서영', + }); + + mockData.push({ + id: 104, + profileImageUrl: `/images/mock_profile.png`, + nickname: '박소현', + }); + + mockData.push({ + id: 105, + profileImageUrl: `/images/mock_profile.png`, + nickname: '강현지', + }); + + mockData.push({ + id: 106, + profileImageUrl: `/images/mock_profile.png`, + nickname: '신은서', + }); + + mockData.push({ + id: 107, + profileImageUrl: `/images/mock_profile.png`, + nickname: '동호', + }); + + mockData.push({ + id: 108, + profileImageUrl: `/images/mock_profile.png`, + nickname: 'JJUNGSU', + }); + + for (let i = 1; i <= count; i++) { + const user: UserProfileType = { + id: i, + profileImageUrl: '', + nickname: `User${i}`, + }; + + mockData.push(user); + } + + return mockData; +}; + +export default generateMockData(20); diff --git a/src/app/create/_components/CreateList.css.ts b/src/app/create/_components/CreateList.css.ts new file mode 100644 index 00000000..98ef3122 --- /dev/null +++ b/src/app/create/_components/CreateList.css.ts @@ -0,0 +1,351 @@ +import { style } from '@vanilla-extract/css'; +import * as GlobalStyles from '@/styles/globalStyles.css'; + +GlobalStyles; + +export const header = style({ + width: '100%', + height: '90px', + paddingLeft: '20px', + paddingRight: '20px', + + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + + borderBottom: '1px solid rgba(0, 0, 0, 0.10)', +}); + +export const headerTitle = style({ + fontSize: '2rem', +}); + +export const headerNextButton = style({ + fontSize: '1.6rem', + color: '#8d8d8d', +}); + +export const body = style({ + width: '100vw', + padding: '37px 20px 100px', + + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + rowGap: '50px', +}); + +export const title = style({ + marginBottom: '16px', + fontSize: '1.6rem', + fontWeight: '600', +}); + +export const required = style({ + color: '#ff0000', +}); + +export const content = style({ + fontSize: '1.5rem', +}); + +export const error = style({ + margin: '10px', + + fontSize: '1.5rem', + + color: 'red', +}); + +export const listTitleContainer = style({ + position: 'relative', +}); + +export const titleInputBox = style({ + width: '100%', + padding: '11px', + + position: 'relative', + + fontSize: '1.5rem', + border: '0px', + borderBottom: '1px solid rgba(0, 0, 0, 0.10)', + outline: 'none', +}); + +export const clearButton = style({ + position: 'absolute', + top: '20px', + right: '8px', + transform: 'translateY(-50%)', + cursor: 'pointer', +}); + +export const listDescriptionContainer = style({ + position: 'relative', +}); + +export const descriptionInputBox = style({ + width: '100%', + padding: '12px', + + fontSize: '1.5rem', + + resize: 'none', + whiteSpace: 'pre-wrap', + // overflowY: 'hidden', + + border: '1px solid rgba(0, 0, 0, 0.10)', + borderRadius: '8px', + + outline: 'none', +}); + +export const dragIcon = style({ + position: 'absolute', + top: '50%', + right: '5px', + cursor: 'pointer', +}); + +export const categoryContainer = style({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + columnGap: '12px', + + overflow: 'auto', + whiteSpace: 'nowrap', + scrollbarWidth: 'none', + '::-webkit-scrollbar': { + width: '0', + }, +}); + +export const categoryInputBox = style({ + display: 'none', +}); + +export const categoryButton = style({ + width: '', + height: '40px', + padding: '8px 12px', + + fontSize: '1.6rem', + fontWeight: '600', + + backgroundColor: 'transparent', + + whiteSpace: 'nowrap', + + border: '1px solid #DEDEDE', + borderRadius: '10px', +}); + +export const categoryButtonActive = style({ + backgroundColor: '#EBF4FF', +}); + +export const labelContainer = style({ + display: 'flex', + flexDirection: 'column', +}); + +export const labelInputBox = style({ + width: '100%', + padding: '10px', + + fontSize: '1.5rem', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + outline: 'none', + cursor: 'pointer', +}); + +export const labels = style({ + marginTop: '10px', + + display: 'flex', + flexDirection: 'row', + columnGap: '5px', +}); + +export const label = style({ + width: 'fit-content', + padding: '7px', + paddingRight: '20px', + + position: 'relative', + + color: '#333333', + backgroundColor: '#EBF4FF', + + fontSize: '1.3rem', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + cursor: 'pointer', +}); + +export const labelDeleteButton = style({ + position: 'absolute', + top: '10px', + right: '5px', + + stroke: '#8E8E93', + strokeWidth: '1.5', + + cursor: 'pointer', +}); + +export const colaboContainer = style({ + position: 'relative', +}); + +export const colaboInputBox = style({ + width: '100%', + padding: '10px', + paddingLeft: '30px', + + fontSize: '1.5rem', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + outline: 'none', + cursor: 'pointer', +}); + +export const colaboDropdown = style({ + height: '152px', + marginTop: '5px', + marginBottom: '10px', + padding: '11px', + + display: 'flex', + flexDirection: 'column', + rowGap: '5px', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + + overflowY: 'auto', +}); + +export const colaboProfileContainer = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + columnGap: '16px', + + fontSize: '1.5rem', +}); + +export const colaboList = style({ + padding: '4.5px', + + display: 'flex', + flexDirection: 'column', + rowGap: '5px', +}); + +export const colaboItem = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', +}); + +export const colaboPlaceholder = style({ + fontSize: '1.5rem', + color: '#61646B', +}); + +export const colaboProfileImage = style({ + borderRadius: '50%', +}); + +export const searchIcon = style({ + width: '15.7px', + height: '15.7px', + + position: 'absolute', + top: '20px', + left: '8px', + transform: 'translateY(-50%)', +}); + +export const backgroundContainer = style({ + display: 'flex', + flexDirection: 'row', + columnGap: '12px', +}); + +export const colorCircle = style({ + width: '50px', + height: '50px', + + appearance: 'none', + MozAppearance: 'none', + WebkitAppearance: 'none', + outline: 'none', + + accentColor: 'red', + backgroundColor: '#ffffff', + + border: '3px #ffffff solid', + WebkitBorderBefore: '3px #ffffff solid', + borderRadius: '50%', + WebkitBorderRadius: '50%', + MozBorderRadius: '50%', + boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', + + cursor: 'pointer', +}); + +export const checkedColor = style({ + borderColor: '#0047FF', +}); + +export const white = style({ + backgroundColor: '#FFFFFF', +}); +export const yellow = style({ + backgroundColor: '#FFF6A5', +}); +export const orange = style({ + backgroundColor: '#FFDCB2', +}); +export const green = style({ + backgroundColor: '#D0FF89', +}); +export const blue = style({ + backgroundColor: '#B7EEFF', +}); +export const purple = style({ + backgroundColor: '#E6C6FF', +}); + +export const publicContainer = style({ + marginBottom: '8px', + + display: 'flex', + flexDirection: 'row', + columnGap: '16px', + + accentColor: 'black', +}); + +export const publicMessage = style({ + marginLeft: '5px', + + fontSize: '1.4rem', + color: '#909090', +}); + +export const checkedIcon = style({ + marginLeft: '5px', + color: '#008000', +}); diff --git a/src/app/create/_components/CreateList.tsx b/src/app/create/_components/CreateList.tsx new file mode 100644 index 00000000..adf67112 --- /dev/null +++ b/src/app/create/_components/CreateList.tsx @@ -0,0 +1,428 @@ +'use client'; + +import React, { useEffect, useRef, useState } from 'react'; +import Link from 'next/link'; +import { useFormContext, useWatch } from 'react-hook-form'; + +import '@/styles/globalStyles.css'; +import * as styles from './CreateList.css'; + +import CloseButton from '/public/icons/close_button.svg'; +import EraseButton from '/public/icons/x_circle_fill.svg'; +import SearchIcon from '/public/icons/search.svg'; +import DefaultProfile from '/public/icons/default_profile.svg'; + +import mockdata from '../CreateListMock'; +import Image from 'next/image'; + +interface UserProfileType { + id: number; + profileImageUrl: string; + nickname: string; +} + +function CreateList() { + const { register, getValues, setValue, setError, control, formState } = useFormContext(); + const { errors, isValid } = formState; + + const category = useWatch({ control, name: 'category' }); + const labels = useWatch({ control, name: 'labels' }); + const colaboIDs = useWatch({ control, name: 'collaboratorIds' }); + const backgroundColor = useWatch({ control, name: 'backgroundColor' }); + const isPublic = useWatch({ control, name: 'isPublic' }); + + const [labelInput, setLabelInput] = useState(''); + const [colaboInput, setColaboInput] = useState(''); + const [colaboList, setColabolist] = useState([]); + const [isDropDownOpen, setIsDropDownOpen] = useState(false); + + const colaboInputRef = useRef(null); + const dropdownRef = useRef(null); + + useEffect(() => { + const closeDropdown = (event: MouseEvent) => { + if ( + dropdownRef.current && + colaboInputRef.current && + !dropdownRef.current.contains(event.target as Node) && + !colaboInputRef.current.contains(event.target as Node) + ) { + setIsDropDownOpen(false); + } + }; + document.addEventListener('click', closeDropdown); + + return () => { + document.removeEventListener('click', closeDropdown); + }; + }, []); + + return ( +
+ {/* 헤더 */} +
+ +

리스트 생성

+ + 다음 + +
+ +
+ {/* 제목 */} +
+
+ 타이틀 * +
+
+
+ + { + setValue('title', ''); + }} + /> + {errors.title &&
{errors.title.message?.toString()}
} +
+
+
+ + {/* 한 줄 소개 */} +
+
소개
+
+
+