From 4b4da376795fbbc0784dc5e06a4b1e96e0224112 Mon Sep 17 00:00:00 2001 From: Kim-jaeyeon Date: Mon, 9 Sep 2024 14:52:46 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Feat[notice]:=20notice=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배경화면 global 설정, 검색기능, 공지사항 목록 초안 --- src/App.tsx | 3 +- src/Notice.tsx | 70 +++++++++ src/assets/background.svg | 80 +++++++++++ src/assets/copyright.svg | 22 +++ src/assets/downbtn.svg | 10 ++ src/assets/search.svg | 3 + src/pages/Notice/index.tsx | 256 ++++++++++++++++++++++++++++++++- src/pages/Notice/notice.css.ts | 179 +++++++++++++++++++++++ vite.config.ts | 1 - 9 files changed, 619 insertions(+), 5 deletions(-) create mode 100644 src/Notice.tsx create mode 100644 src/assets/background.svg create mode 100644 src/assets/copyright.svg create mode 100644 src/assets/downbtn.svg create mode 100644 src/assets/search.svg create mode 100644 src/pages/Notice/notice.css.ts diff --git a/src/App.tsx b/src/App.tsx index f6053e9..2ec9ece 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -46,7 +46,7 @@ const RouterPath = [ element: , }, { - paht: "QnA", + path: "QnA", element: , }, { @@ -65,5 +65,4 @@ const router = createBrowserRouter([ function App() { return ; } - export default App; diff --git a/src/Notice.tsx b/src/Notice.tsx new file mode 100644 index 0000000..bc41049 --- /dev/null +++ b/src/Notice.tsx @@ -0,0 +1,70 @@ +// App.tsx +import React, { useState } from "react"; +import { + container, + mid, + notice, + noticeList, + noticeContainer, + searchbar, + searchButton, + searchContainer, +} from "./pages/Notice/notice.css.ts"; // 스타일 가져오기 + +function Notice() { + const [query, setQuery] = useState(""); + + // 검색어 입력 시 상태 업데이트 + const handleSearch = (event: React.ChangeEvent) => { + setQuery(event.target.value); + }; + + // 버튼 클릭 시 실행할 로직 + const handleButtonClick = () => { + alert(`Searching for: ${query}`); + }; + + return ( +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} + +export default Notice; diff --git a/src/assets/background.svg b/src/assets/background.svg new file mode 100644 index 0000000..ef32578 --- /dev/null +++ b/src/assets/background.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/copyright.svg b/src/assets/copyright.svg new file mode 100644 index 0000000..ff9bac2 --- /dev/null +++ b/src/assets/copyright.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/downbtn.svg b/src/assets/downbtn.svg new file mode 100644 index 0000000..37638e2 --- /dev/null +++ b/src/assets/downbtn.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/search.svg b/src/assets/search.svg new file mode 100644 index 0000000..77f123c --- /dev/null +++ b/src/assets/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/Notice/index.tsx b/src/pages/Notice/index.tsx index e2ac67b..0a0d183 100644 --- a/src/pages/Notice/index.tsx +++ b/src/pages/Notice/index.tsx @@ -1,7 +1,259 @@ -export default function Notice() { +import React, { useState, useEffect, useRef } from "react"; +import { + container, + mid, + notice, + noticeList, + noticeContentWrapper, // 추가된 래퍼 스타일 + noticeContent, + noticeContainer, + searchbar, + searchButton, + searchContainer, + copyright, + noticeNumber, + noticeDownBtn, + noticeDetail, +} from "./notice.css.ts"; // 스타일 가져오기 + +// NoticeItem 타입 정의: 제목은 string, 콘텐츠는 JSX.Element로 지정 +interface NoticeItem { + id: number; + title: string; + content: JSX.Element; // 콘텐츠는 JSX로 처리 + detail: JSX.Element; // 상세 내용을 위한 필드 추가 +} + +// 임시 데이터 예시 +const noticeData: NoticeItem[] = [ + { + id: 1, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This 강원대 the content for notice 1.

, + detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + }, + { + id: 2, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + }, + { + id: 3, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This 강원대 the content for notice 1.

, + detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + }, + { + id: 4, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + }, + { + id: 5, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This 강원대 the content for notice 1.

, + detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + }, + { + id: 6, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + }, + { + id: 7, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This 강원대 the content for notice 1.

, + detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + }, + { + id: 8, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + }, + { + id: 9, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This 강원대 the content for notice 1.

, + detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + }, + { + id: 10, + title: "2024 강원대학교 백령대동제 개최 안내", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.

, + }, + // 나머지 공지사항 추가 +]; + +function Notice() { + const [query, setQuery] = useState(""); // 검색어의 타입을 string으로 지정 + const [currentPage, setCurrentPage] = useState(1); // 현재 페이지 상태 + const [expandedNotices, setExpandedNotices] = useState([]); // 확장된 공지 ID들을 관리 + const itemsPerPage = 6; // 한 페이지에 표시할 최대 noticeContainer 개수 + + // 각 공지사항의 애니메이션 상태를 관리하는 배열 + const [animated, setAnimated] = useState(Array(noticeData.length).fill(false)); + + // 공지사항의 텍스트 길이를 확인하여 애니메이션을 적용할지 여부를 결정 + const noticeRefs = useRef<(HTMLDivElement | null)[]>([]); + + useEffect(() => { + noticeRefs.current.forEach((ref, index) => { + if (ref) { + const contentWidth = ref.scrollWidth; + const wrapperWidth = ref.clientWidth; + // 텍스트가 컨테이너를 넘으면 애니메이션을 적용 + setAnimated((prev) => { + const updated = [...prev]; + updated[index] = contentWidth > wrapperWidth; + return updated; + }); + } + }); + }, [query, currentPage]); + + // 검색어 입력 시 상태 업데이트 + const handleSearch = (event: React.ChangeEvent) => { + setQuery(event.target.value); + setCurrentPage(1); // 검색어가 변경되면 첫 페이지로 이동 + }; + + // downbtn 클릭 시 상세 내용을 보여주는 로직 + const toggleNotice = (id: number) => { + setExpandedNotices((prev) => + prev.includes(id) ? prev.filter((noticeId) => noticeId !== id) : [...prev, id], + ); + }; + + // 필터링 로직: query가 있으면 해당 키워드가 포함된 공지만, 없으면 모든 공지를 보여줌 + const filteredNotices = noticeData + .filter((noticeItem) => { + const searchText = query.toLowerCase(); // 검색어를 소문자로 변환 + return ( + noticeItem.title.toLowerCase().includes(searchText) || // 제목에 검색어가 포함되었는지 확인 + noticeItem.detail.props.children.toString().toLowerCase().includes(searchText) // 내용에 검색어가 포함되었는지 확인 + ); + }) + .reverse(); // 공지사항을 내림차순으로 정렬 (마지막 공지사항이 제일 위로 오게) + + // 현재 페이지에 맞는 데이터 추출 + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentNotices = filteredNotices.slice(indexOfFirstItem, indexOfLastItem); + + // 페이지 수 계산 + const totalPages = Math.ceil(filteredNotices.length / itemsPerPage); + + // 페이지 변경 함수 + const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + return (
-

Notice

+
+
+
+ + +
+ + {/* noticeList 렌더링 */} +
+ {filteredNotices.length === 0 ? ( +

+ 해당하는 게시글이 없어요! +

+ ) : ( + currentNotices.map((noticeItem, index) => ( +
+
+ {/* 번호와 제목 표시 */} +
+

{index + 1 + (currentPage - 1) * itemsPerPage}

+ {/* noticeNumber가 1인 게시글에만 'New' 표시 */} + {index + 1 + (currentPage - 1) * itemsPerPage === 1 && ( + + New + + )} +
+ {/* 공지사항 콘텐츠에 스크롤 애니메이션 추가 */} +
(noticeRefs.current[index] = el)} + > +

+ {noticeItem.title} +

+
+ {/* downbtn을 클릭하면 토글 */} + +
+ {/* 상세 내용 표시 - 확장 시만 보여줌 */} + {expandedNotices.includes(noticeItem.id) && ( +
{noticeItem.detail}
+ )} +
+ )) + )} +
+ + {/* 페이지네이션 */} + {filteredNotices.length > 0 && ( +
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((pageNumber) => ( + + ))} +
+ )} +
+ +
+
+
); } + +export default Notice; \ No newline at end of file diff --git a/src/pages/Notice/notice.css.ts b/src/pages/Notice/notice.css.ts new file mode 100644 index 0000000..812ec63 --- /dev/null +++ b/src/pages/Notice/notice.css.ts @@ -0,0 +1,179 @@ +import { style, globalStyle, keyframes } from "@vanilla-extract/css"; + +// body에 글로벌 스타일 적용 +globalStyle("body", { + margin: 0, + padding: 0, + backgroundImage: 'url("src/assets/background.svg")', // 배경 이미지 경로 + backgroundSize: "cover", // 배경 이미지를 화면 전체에 맞춤 + backgroundPosition: "center", // 이미지를 화면 중앙에 위치 + backgroundRepeat: "no-repeat", // 이미지를 반복하지 않도록 설정 + width: "100%", + height: "100vh", // 전체 화면 높이 적용 + overflow: "hidden", // 스크롤바가 나타나지 않도록 설정 + fontFamily: `'Pretendard', sans-serif`, +}); + +globalStyle("::-webkit-scrollbar", { + width: "0px", + height: "0px", +}); + +// 검색 필드와 버튼을 감싸는 컨테이너 +export const searchContainer = style({ + display: "flex", + alignItems: "center", + backgroundColor: "white", + borderRadius: "50px", + boxShadow: "0 5px 5px rgba(0,0,0,0.25)", + width: "80%", + height: "40px", + position: "relative", + marginBottom: 20, +}); + +// 검색 입력 필드 스타일 +export const searchbar = style({ + width: "85%", // 너비 조정 + height: "100%", + border: "none", + outline: "none", + borderRadius: "50px 0 0 50px", // 왼쪽만 둥글게 + paddingLeft: "15px", + fontFamily: `'Pretendard', sans-serif`, + fontSize: "18px", + fontWeight: 500, +}); + +// 검색 버튼 스타일 +export const searchButton = style({ + width: "70px", // 버튼의 너비 + height: 42, + backgroundColor: "#3498db", // 파란색 배경 + border: "none", + borderRadius: 20, + cursor: "pointer", + display: "flex", + justifyContent: "center", + alignItems: "center", + color: "white", + fontSize: "20px", + padding: "0", + boxShadow: "0 5px 5px rgba(0,0,0,0.25)", + position: "absolute", + right: 0, +}); + +// 상단의 빨간 컨테이너 +export const container = style({ + width: "100%", + height: 110, + display: "flex", + justifyContent: "center", +}); + +// 중간에 검색창을 배치하는 컨테이너 +export const mid = style({ + width: "100%", + height: 712, + display: "flex", + justifyContent: "center", + flexDirection: "column", + alignItems: "center", +}); + +export const noticeList = style({ + width: "100%", + height: "70%", + overflowY: "scroll", + display: "flex", + alignItems: "center", + flexDirection: "column", +}); + +export const noticeContainer = style({ + width: "95%", + height: "auto", + borderBottom: "1px solid white", + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column", + marginBottom: 7, +}); + +export const notice = style({ + width: "95%", + height: 60, + backgroundColor: "white", + borderRadius: 10, + marginBottom: 7, + display: "flex", + alignItems: "center", + flexDirection: "row", + position: "relative", +}); + +export const copyright = style({ + margin: 0, + padding: 0, + backgroundImage: 'url("src/assets/copyright.svg")', // 배경 이미지 경로 + backgroundPosition: "center", // 이미지를 화면 중앙에 위치 + backgroundRepeat: "no-repeat", // 이미지를 반복하지 않도록 설정 + width: "80%", + height: "100%", // 전체 화면 높이 적용 + overflow: "hidden", // 스크롤바가 나타나지 않도록 설정 +}); + +export const noticeNumber = style({ + fontSize: 25, + fontWeight: 700, + color: "#0081C9", + marginLeft: 20, +}); +// 텍스트를 스크롤시키는 애니메이션 +const scrollAnimation = keyframes({ + "0%": { transform: "translateX(100%)" }, + "100%": { transform: "translateX(-100%)" }, +}); + +export const noticeContentWrapper = style({ + width: "70%", // 부모 요소의 너비에 맞추기 + overflow: "hidden", // 넘치는 부분을 숨김 + position: "relative", + marginLeft: 10, +}); + +export const noticeContent = style({ + display: "inline-block", // 텍스트가 한 줄로 스크롤되도록 설정 + whiteSpace: "nowrap", // 텍스트를 한 줄로 유지 + animation: `${scrollAnimation} 8s linear infinite`, // 10초 동안 좌우로 스크롤 + fontSize: 18, + fontWeight: 500, + color: "black", + margin: 10, +}); +export const noticeDownBtn = style({ + backgroundImage: 'url("src/assets/downbtn.svg")', // 배경 이미지 경로 + backgroundSize: "cover", // 배경 이미지를 화면 전체에 맞춤 + backgroundPosition: "center", // 이미지를 화면 중앙에 위치 + backgroundRepeat: "no-repeat", // 이미지를 반복하지 않도록 설정 + backgroundColor: "white", + width: 30, + height: 30, + position: "absolute", + right: 10, + border: "none", +}); + +// 공지사항 상세 내용 (애니메이션 효과 포함) +export const noticeDetail = style({ + overflow: "hidden", + transition: "max-height 0.5s ease-in-out", // 부드러운 애니메이션 효과 + maxHeight: "500px", // 최대 높이를 설정 + backgroundColor: "rgba(255,255,255,0.6)", // 배경색 + padding: "10px", + marginTop: -12, + marginBottom: 5, + width: "90%", +}); diff --git a/vite.config.ts b/vite.config.ts index bf4565d..dd7d7fa 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vite"; import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; import react from "@vitejs/plugin-react"; -// https://vitejs.dev/config/ export default defineConfig({ plugins: [react(), vanillaExtractPlugin()], }); From ff6541c0102bc26db8e19afae066a1b9c04dc9a9 Mon Sep 17 00:00:00 2001 From: Kim-jaeyeon Date: Mon, 9 Sep 2024 21:27:09 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(notice=5Fpage):=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=EC=84=B1=20,=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 키워드 입력시 실시간으로 해당하는 게시글이 뜨도록 변경+검색버튼 삭제, 공지사항 내용은 아직 구현 전. 페이지네이션 구현 --- src/assets/copyright.svg | 14 ++--- src/pages/Notice/index.tsx | 108 ++++++++++++--------------------- src/pages/Notice/notice.css.ts | 61 ++++++++----------- 3 files changed, 70 insertions(+), 113 deletions(-) diff --git a/src/assets/copyright.svg b/src/assets/copyright.svg index ff9bac2..ee61103 100644 --- a/src/assets/copyright.svg +++ b/src/assets/copyright.svg @@ -1,11 +1,11 @@ - - + + - - + + - - + + @@ -14,7 +14,7 @@ - + diff --git a/src/pages/Notice/index.tsx b/src/pages/Notice/index.tsx index 0a0d183..365ec19 100644 --- a/src/pages/Notice/index.tsx +++ b/src/pages/Notice/index.tsx @@ -1,14 +1,14 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState } from "react"; import { container, + titleText, mid, notice, noticeList, - noticeContentWrapper, // 추가된 래퍼 스타일 + noticeContentWrapper, noticeContent, noticeContainer, searchbar, - searchButton, searchContainer, copyright, noticeNumber, @@ -20,8 +20,8 @@ import { interface NoticeItem { id: number; title: string; - content: JSX.Element; // 콘텐츠는 JSX로 처리 - detail: JSX.Element; // 상세 내용을 위한 필드 추가 + content: JSX.Element; + detail: JSX.Element; } // 임시 데이터 예시 @@ -30,59 +30,53 @@ const noticeData: NoticeItem[] = [ id: 1, title: "2024 강원대학교 백령대동제 개최 안내", content:

This 강원대 the content for notice 1.

, - detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + detail:

여기는 마지막 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, }, { id: 2, title: "2024 강원대학교 백령대동제 개최 안내", content:

This is the content for notice 2.

, - detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + detail:

여기는 룰루 내용입니다.

, }, { id: 3, - title: "2024 강원대학교 백령대동제 개최 안내", - content:

This 강원대 the content for notice 1.

, - detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + title: "랄랄라 신규 게시물", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.

, }, { id: 4, title: "2024 강원대학교 백령대동제 개최 안내", content:

This is the content for notice 2.

, - detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + detail:

여기는 룰루 내용입니다.

, }, { id: 5, - title: "2024 강원대학교 백령대동제 개최 안내", - content:

This 강원대 the content for notice 1.

, - detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + title: "랄랄라 신규 게시물", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.

, }, { id: 6, title: "2024 강원대학교 백령대동제 개최 안내", content:

This is the content for notice 2.

, - detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + detail:

여기는 룰루 내용입니다.

, }, { id: 7, - title: "2024 강원대학교 백령대동제 개최 안내", - content:

This 강원대 the content for notice 1.

, - detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, + title: "랄랄라 신규 게시물", + content:

This is the content for notice 2.

, + detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.

, }, { id: 8, title: "2024 강원대학교 백령대동제 개최 안내", content:

This is the content for notice 2.

, - detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다.

, + detail:

여기는 룰루 내용입니다.

, }, { id: 9, - title: "2024 강원대학교 백령대동제 개최 안내", - content:

This 강원대 the content for notice 1.

, - detail:

여기는 강원대 내용입니다. Notice 1에 대한 정보가 더 있습니다.

, - }, - { - id: 10, - title: "2024 강원대학교 백령대동제 개최 안내", + title: "랄랄라 신규 게시물", content:

This is the content for notice 2.

, detail:

여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.

, }, @@ -93,49 +87,31 @@ function Notice() { const [query, setQuery] = useState(""); // 검색어의 타입을 string으로 지정 const [currentPage, setCurrentPage] = useState(1); // 현재 페이지 상태 const [expandedNotices, setExpandedNotices] = useState([]); // 확장된 공지 ID들을 관리 - const itemsPerPage = 6; // 한 페이지에 표시할 최대 noticeContainer 개수 - - // 각 공지사항의 애니메이션 상태를 관리하는 배열 - const [animated, setAnimated] = useState(Array(noticeData.length).fill(false)); + const itemsPerPage = 6; - // 공지사항의 텍스트 길이를 확인하여 애니메이션을 적용할지 여부를 결정 - const noticeRefs = useRef<(HTMLDivElement | null)[]>([]); - - useEffect(() => { - noticeRefs.current.forEach((ref, index) => { - if (ref) { - const contentWidth = ref.scrollWidth; - const wrapperWidth = ref.clientWidth; - // 텍스트가 컨테이너를 넘으면 애니메이션을 적용 - setAnimated((prev) => { - const updated = [...prev]; - updated[index] = contentWidth > wrapperWidth; - return updated; - }); - } - }); - }, [query, currentPage]); + // 가장 최근 게시물의 ID 찾기 (ID가 제일 큰 것이 최신 게시물이라고 가정) + const mostRecentNoticeId = Math.max(...noticeData.map((notice) => notice.id)); // 검색어 입력 시 상태 업데이트 const handleSearch = (event: React.ChangeEvent) => { setQuery(event.target.value); - setCurrentPage(1); // 검색어가 변경되면 첫 페이지로 이동 + setCurrentPage(1); }; // downbtn 클릭 시 상세 내용을 보여주는 로직 const toggleNotice = (id: number) => { setExpandedNotices((prev) => - prev.includes(id) ? prev.filter((noticeId) => noticeId !== id) : [...prev, id], + prev.includes(id) ? prev.filter((noticeId) => noticeId !== id) : [...prev, id] ); }; - // 필터링 로직: query가 있으면 해당 키워드가 포함된 공지만, 없으면 모든 공지를 보여줌 + // 필터링 로직 const filteredNotices = noticeData .filter((noticeItem) => { - const searchText = query.toLowerCase(); // 검색어를 소문자로 변환 + const searchText = query.toLowerCase(); return ( - noticeItem.title.toLowerCase().includes(searchText) || // 제목에 검색어가 포함되었는지 확인 - noticeItem.detail.props.children.toString().toLowerCase().includes(searchText) // 내용에 검색어가 포함되었는지 확인 + noticeItem.title.toLowerCase().includes(searchText) || + noticeItem.detail.props.children.toString().toLowerCase().includes(searchText) ); }) .reverse(); // 공지사항을 내림차순으로 정렬 (마지막 공지사항이 제일 위로 오게) @@ -153,7 +129,9 @@ function Notice() { return (
-
+
+

공지사항

+
-
{/* noticeList 렌더링 */} @@ -181,8 +156,8 @@ function Notice() { {/* 번호와 제목 표시 */}

{index + 1 + (currentPage - 1) * itemsPerPage}

- {/* noticeNumber가 1인 게시글에만 'New' 표시 */} - {index + 1 + (currentPage - 1) * itemsPerPage === 1 && ( + {/* 가장 최근 게시물에만 'New' 표시 */} + {noticeItem.id === mostRecentNoticeId && ( )}
- {/* 공지사항 콘텐츠에 스크롤 애니메이션 추가 */} -
(noticeRefs.current[index] = el)} - > -

- {noticeItem.title} -

+ +
+

{noticeItem.title}

{/* downbtn을 클릭하면 토글 */}
+ {/* 상세 내용 표시 - 확장 시만 보여줌 */} {expandedNotices.includes(noticeItem.id) && (
{noticeItem.detail}
diff --git a/src/pages/Notice/notice.css.ts b/src/pages/Notice/notice.css.ts index 812ec63..3b88da5 100644 --- a/src/pages/Notice/notice.css.ts +++ b/src/pages/Notice/notice.css.ts @@ -2,6 +2,7 @@ import { style, globalStyle, keyframes } from "@vanilla-extract/css"; // body에 글로벌 스타일 적용 globalStyle("body", { + position:'relative', margin: 0, padding: 0, backgroundImage: 'url("src/assets/background.svg")', // 배경 이미지 경로 @@ -19,6 +20,12 @@ globalStyle("::-webkit-scrollbar", { height: "0px", }); +export const titleText=style({ + fontSize:20, + fontWeight:600, + color:'white', + marginTop:60, +}) // 검색 필드와 버튼을 감싸는 컨테이너 export const searchContainer = style({ display: "flex", @@ -28,41 +35,25 @@ export const searchContainer = style({ boxShadow: "0 5px 5px rgba(0,0,0,0.25)", width: "80%", height: "40px", - position: "relative", + position: "fixed", + top:150, marginBottom: 20, }); // 검색 입력 필드 스타일 export const searchbar = style({ - width: "85%", // 너비 조정 + width: "100%", // 너비 조정 height: "100%", border: "none", outline: "none", - borderRadius: "50px 0 0 50px", // 왼쪽만 둥글게 - paddingLeft: "15px", + borderRadius: 50, // 왼쪽만 둥글게 + padding: "0px 15px", fontFamily: `'Pretendard', sans-serif`, fontSize: "18px", fontWeight: 500, }); -// 검색 버튼 스타일 -export const searchButton = style({ - width: "70px", // 버튼의 너비 - height: 42, - backgroundColor: "#3498db", // 파란색 배경 - border: "none", - borderRadius: 20, - cursor: "pointer", - display: "flex", - justifyContent: "center", - alignItems: "center", - color: "white", - fontSize: "20px", - padding: "0", - boxShadow: "0 5px 5px rgba(0,0,0,0.25)", - position: "absolute", - right: 0, -}); + // 상단의 빨간 컨테이너 export const container = style({ @@ -74,9 +65,11 @@ export const container = style({ // 중간에 검색창을 배치하는 컨테이너 export const mid = style({ + position:'relative', width: "100%", - height: 712, + height: 650, display: "flex", + margin:'20px 0', justifyContent: "center", flexDirection: "column", alignItems: "center", @@ -84,7 +77,7 @@ export const mid = style({ export const noticeList = style({ width: "100%", - height: "70%", + height: 455, overflowY: "scroll", display: "flex", alignItems: "center", @@ -115,13 +108,15 @@ export const notice = style({ }); export const copyright = style({ + position:'absolute', + bottom:0, margin: 0, padding: 0, backgroundImage: 'url("src/assets/copyright.svg")', // 배경 이미지 경로 backgroundPosition: "center", // 이미지를 화면 중앙에 위치 backgroundRepeat: "no-repeat", // 이미지를 반복하지 않도록 설정 - width: "80%", - height: "100%", // 전체 화면 높이 적용 + width: "90%", + height: 110, // 전체 화면 높이 적용 overflow: "hidden", // 스크롤바가 나타나지 않도록 설정 }); @@ -131,14 +126,10 @@ export const noticeNumber = style({ color: "#0081C9", marginLeft: 20, }); -// 텍스트를 스크롤시키는 애니메이션 -const scrollAnimation = keyframes({ - "0%": { transform: "translateX(100%)" }, - "100%": { transform: "translateX(-100%)" }, -}); + export const noticeContentWrapper = style({ - width: "70%", // 부모 요소의 너비에 맞추기 + width: "90%", // 부모 요소의 너비에 맞추기 overflow: "hidden", // 넘치는 부분을 숨김 position: "relative", marginLeft: 10, @@ -147,11 +138,10 @@ export const noticeContentWrapper = style({ export const noticeContent = style({ display: "inline-block", // 텍스트가 한 줄로 스크롤되도록 설정 whiteSpace: "nowrap", // 텍스트를 한 줄로 유지 - animation: `${scrollAnimation} 8s linear infinite`, // 10초 동안 좌우로 스크롤 - fontSize: 18, + fontSize: 16, fontWeight: 500, color: "black", - margin: 10, + margin: 5, }); export const noticeDownBtn = style({ backgroundImage: 'url("src/assets/downbtn.svg")', // 배경 이미지 경로 @@ -169,7 +159,6 @@ export const noticeDownBtn = style({ // 공지사항 상세 내용 (애니메이션 효과 포함) export const noticeDetail = style({ overflow: "hidden", - transition: "max-height 0.5s ease-in-out", // 부드러운 애니메이션 효과 maxHeight: "500px", // 최대 높이를 설정 backgroundColor: "rgba(255,255,255,0.6)", // 배경색 padding: "10px", From 419ce392dcf69ce0134ec5a5fc39a05301cee594 Mon Sep 17 00:00:00 2001 From: Kim-jaeyeon Date: Tue, 10 Sep 2024 16:08:28 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Fix(overwriting):=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=EB=90=9C=20notice=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 업스트림 최신버전 fetch 완료, 중복된 컴포넌트 삭제, npm run prettier --- src/Notice.tsx | 70 ---------------------------------- src/pages/Notice/index.tsx | 4 +- src/pages/Notice/notice.css.ts | 27 ++++++------- 3 files changed, 14 insertions(+), 87 deletions(-) delete mode 100644 src/Notice.tsx diff --git a/src/Notice.tsx b/src/Notice.tsx deleted file mode 100644 index bc41049..0000000 --- a/src/Notice.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// App.tsx -import React, { useState } from "react"; -import { - container, - mid, - notice, - noticeList, - noticeContainer, - searchbar, - searchButton, - searchContainer, -} from "./pages/Notice/notice.css.ts"; // 스타일 가져오기 - -function Notice() { - const [query, setQuery] = useState(""); - - // 검색어 입력 시 상태 업데이트 - const handleSearch = (event: React.ChangeEvent) => { - setQuery(event.target.value); - }; - - // 버튼 클릭 시 실행할 로직 - const handleButtonClick = () => { - alert(`Searching for: ${query}`); - }; - - return ( -
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ); -} - -export default Notice; diff --git a/src/pages/Notice/index.tsx b/src/pages/Notice/index.tsx index 365ec19..c5c5d4a 100644 --- a/src/pages/Notice/index.tsx +++ b/src/pages/Notice/index.tsx @@ -101,7 +101,7 @@ function Notice() { // downbtn 클릭 시 상세 내용을 보여주는 로직 const toggleNotice = (id: number) => { setExpandedNotices((prev) => - prev.includes(id) ? prev.filter((noticeId) => noticeId !== id) : [...prev, id] + prev.includes(id) ? prev.filter((noticeId) => noticeId !== id) : [...prev, id], ); }; @@ -224,4 +224,4 @@ function Notice() { ); } -export default Notice; \ No newline at end of file +export default Notice; diff --git a/src/pages/Notice/notice.css.ts b/src/pages/Notice/notice.css.ts index 3b88da5..a57f646 100644 --- a/src/pages/Notice/notice.css.ts +++ b/src/pages/Notice/notice.css.ts @@ -2,7 +2,7 @@ import { style, globalStyle, keyframes } from "@vanilla-extract/css"; // body에 글로벌 스타일 적용 globalStyle("body", { - position:'relative', + position: "relative", margin: 0, padding: 0, backgroundImage: 'url("src/assets/background.svg")', // 배경 이미지 경로 @@ -20,12 +20,12 @@ globalStyle("::-webkit-scrollbar", { height: "0px", }); -export const titleText=style({ - fontSize:20, - fontWeight:600, - color:'white', - marginTop:60, -}) +export const titleText = style({ + fontSize: 20, + fontWeight: 600, + color: "white", + marginTop: 60, +}); // 검색 필드와 버튼을 감싸는 컨테이너 export const searchContainer = style({ display: "flex", @@ -36,7 +36,7 @@ export const searchContainer = style({ width: "80%", height: "40px", position: "fixed", - top:150, + top: 150, marginBottom: 20, }); @@ -53,8 +53,6 @@ export const searchbar = style({ fontWeight: 500, }); - - // 상단의 빨간 컨테이너 export const container = style({ width: "100%", @@ -65,11 +63,11 @@ export const container = style({ // 중간에 검색창을 배치하는 컨테이너 export const mid = style({ - position:'relative', + position: "relative", width: "100%", height: 650, display: "flex", - margin:'20px 0', + margin: "20px 0", justifyContent: "center", flexDirection: "column", alignItems: "center", @@ -108,8 +106,8 @@ export const notice = style({ }); export const copyright = style({ - position:'absolute', - bottom:0, + position: "absolute", + bottom: 0, margin: 0, padding: 0, backgroundImage: 'url("src/assets/copyright.svg")', // 배경 이미지 경로 @@ -127,7 +125,6 @@ export const noticeNumber = style({ marginLeft: 20, }); - export const noticeContentWrapper = style({ width: "90%", // 부모 요소의 너비에 맞추기 overflow: "hidden", // 넘치는 부분을 숨김 From 58f102ae45b5de5d0cae822ccaa9de13d7c94601 Mon Sep 17 00:00:00 2001 From: Kim-jaeyeon Date: Tue, 10 Sep 2024 16:21:50 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Hotfix(keyFrames=20=EC=A0=9C=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 안 쓰는 거라 삭제 --- src/pages/Notice/notice.css.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Notice/notice.css.ts b/src/pages/Notice/notice.css.ts index a57f646..4ce0b9d 100644 --- a/src/pages/Notice/notice.css.ts +++ b/src/pages/Notice/notice.css.ts @@ -1,4 +1,4 @@ -import { style, globalStyle, keyframes } from "@vanilla-extract/css"; +import { style, globalStyle } from "@vanilla-extract/css"; // body에 글로벌 스타일 적용 globalStyle("body", {