diff --git a/next.config.ts b/next.config.ts index a5da119..0ecd118 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,7 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { + // TurboPack 설정 experimental: { turbo: { diff --git a/src/api/test.tsx b/src/api/test.tsx deleted file mode 100644 index 5281734..0000000 --- a/src/api/test.tsx +++ /dev/null @@ -1,4 +0,0 @@ -function test() { - return
test
; -} -export default test; diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx index d02acd8..84390d4 100644 --- a/src/app/community/page.tsx +++ b/src/app/community/page.tsx @@ -1,4 +1,37 @@ +import CommunityFilter from '@/shared/components/community/CommunityFilter'; +import CommunityHeader from '@/shared/components/community/CommunityHeader'; +import CommunityTab from '@/shared/components/community/CommunityTab'; +import PostCard from '@/shared/components/community/PostCard'; +import WriteBtn from '@/shared/components/community/WriteBtn'; + function Page() { - return
; + return ( +
+
+
+

+ 커뮤니티 페이지 +

+ +
+ +
+ + +
+ +
+ + + + + +
+
+
+ ); } export default Page; diff --git a/src/app/recipe/components/Accordion.tsx b/src/app/recipe/components/Accordion.tsx new file mode 100644 index 0000000..aff8c14 --- /dev/null +++ b/src/app/recipe/components/Accordion.tsx @@ -0,0 +1,36 @@ +'use client'; + +import SelectBox from '@/shared/components/InputBox/SelectBox'; + +const selectOption = [ + { + id: 'abv', + option: ['', '약한 도수', '가벼운 도수', '중간 도수', '센 도수', '매우 센 도수'], + title: '도수', + }, + { + id: 'base', + option: ['', '위스키', '진', '럼', '보드카', '데킬라', '리큐르'], + title: '베이스', + }, + { + id: 'glass', + option: ['', '클래식', '롱', '슈터', '숏'], + title: '글라스', + }, +]; + +function Accordion() { + return ( + + ); +} +export default Accordion; diff --git a/src/app/recipe/page.tsx b/src/app/recipe/page.tsx index 425868e..9f5e417 100644 --- a/src/app/recipe/page.tsx +++ b/src/app/recipe/page.tsx @@ -1,6 +1,10 @@ import PageHeader from '@/shared/components/pageHeader/PageHeader'; import { Metadata } from 'next'; -import Glass from '@/shared/assets/images/recipe_page_header.png'; +import Glass from '@/shared/assets/images/recipe_page_header.webp'; +import SelectBox from '@/shared/components/InputBox/SelectBox'; +import Input from '@/shared/components/InputBox/Input'; +import CocktailList from '@/shared/components/recipePage/cocktailList/CocktailList'; +import Accordion from './components/Accordion'; export const metadata: Metadata = { title: 'SSOUL | 칵테일레시피', @@ -10,12 +14,33 @@ export const metadata: Metadata = { function Page() { return (
- -
+
+ +
+
+
+ + +
+
+
+

n개

+ +
+
+ +
+
+
); } diff --git a/src/shared/@store/accordionStore.ts b/src/shared/@store/accordionStore.ts new file mode 100644 index 0000000..5739eb9 --- /dev/null +++ b/src/shared/@store/accordionStore.ts @@ -0,0 +1,32 @@ +// zustand +import { create } from 'zustand'; + +// select박스 아코디언 메뉴 +export type ID = string | number; + +type AccordionState = { + openByGroup: Record; +}; + +type AccordionAction = { + setOpen: (group: string, id: ID | null) => void; + toggle: (group: string, id: ID) => void; + closeGroup: (group: string) => void; +}; + +type Accordion = AccordionState & AccordionAction; + +export const useAccordionStore = create((set) => ({ + openByGroup: {}, + setOpen: (group, id) => set((s) => ({ openByGroup: { ...s.openByGroup, [group]: id } })), + + // 같은 id가 이미 열려있으면 닫고 id 교체 + toggle: (group, id) => + set((s) => { + const cur = s.openByGroup[group] ?? null; + return { openByGroup: { ...s.openByGroup, [group]: cur === id ? null : id } }; + }), + + // 선택 후 닫기 + closeGroup: (group) => set((s) => ({ openByGroup: { ...s.openByGroup, [group]: null } })), +})); diff --git a/src/shared/@store/store.ts b/src/shared/@store/store.ts deleted file mode 100644 index b9b9f5c..0000000 --- a/src/shared/@store/store.ts +++ /dev/null @@ -1 +0,0 @@ -// zustand store diff --git a/src/shared/assets/images/dummy/exampleCocktail.png b/src/shared/assets/images/dummy/exampleCocktail.png new file mode 100644 index 0000000..a8cb859 Binary files /dev/null and b/src/shared/assets/images/dummy/exampleCocktail.png differ diff --git a/src/shared/assets/images/prepost_img.webp b/src/shared/assets/images/prepost_img.webp new file mode 100644 index 0000000..7c659d7 Binary files /dev/null and b/src/shared/assets/images/prepost_img.webp differ diff --git a/src/shared/assets/images/recipe_page_header.png b/src/shared/assets/images/recipe_page_header.png deleted file mode 100644 index c8b1b96..0000000 Binary files a/src/shared/assets/images/recipe_page_header.png and /dev/null differ diff --git a/src/shared/assets/images/recipe_page_header.webp b/src/shared/assets/images/recipe_page_header.webp new file mode 100644 index 0000000..1bdbf4f Binary files /dev/null and b/src/shared/assets/images/recipe_page_header.webp differ diff --git a/src/shared/components/InputBox/SelectBox.tsx b/src/shared/components/InputBox/SelectBox.tsx index e457eb9..dfeb341 100644 --- a/src/shared/components/InputBox/SelectBox.tsx +++ b/src/shared/components/InputBox/SelectBox.tsx @@ -1,35 +1,65 @@ -import { Ref, useState } from 'react'; +'use client'; +import { Ref, useMemo, useState } from 'react'; import Down from '@/shared/assets/icons/selectDown_24.svg'; +import { ID, useAccordionStore } from '@/shared/@store/accordionStore'; +import { useShallow } from 'zustand/shallow'; interface Props { + id?: ID; + groupKey?: string; ref?: Ref; option: string[]; title: string; + onChange?: (value: string) => void; } -function SelectBox({ ref, option, title }: Props) { +// groupKey를 Props로 내릴경우 == 아코디언 없는 경우 == select박스 +function SelectBox({ id, groupKey, ref, option, title, onChange }: Props) { const [isOpen, setIsOpen] = useState(false); const [select, setSelect] = useState(''); + const ingroup = !!groupKey; + // groupkey일 경우 전달받은 ID로 식별 아닐경우 title로 식별 + const keyId = useMemo(() => id ?? title, [id, title]); + + const { openId, toggleGroup, closeGroup } = useAccordionStore( + useShallow((s) => ({ + openId: ingroup ? (s.openByGroup[groupKey] ?? null) : null, + toggleGroup: s.toggle, + closeGroup: s.closeGroup, + })) + ); + + //groupkey가 있을 떄와 없을때로 구분해서 state혹은 store로 관리 + const localOpen = ingroup ? openId === keyId : isOpen; + + const toggle = () => { + if (ingroup) toggleGroup(groupKey, keyId); + else setIsOpen((v) => !v); + }; + + const close = () => { + if (ingroup) closeGroup(groupKey); + else setIsOpen(false); + }; + const handleChoose = (v: string) => { - setIsOpen(!isOpen); - if (!v) { - setSelect(title); - } else { - setSelect(v); - } + const value = v || title; + setSelect(value); + onChange?.(value); + close(); }; return ( -
+
    {option.map((v, i) => ( diff --git a/src/shared/components/community/CommunityFilter.tsx b/src/shared/components/community/CommunityFilter.tsx new file mode 100644 index 0000000..c8deedd --- /dev/null +++ b/src/shared/components/community/CommunityFilter.tsx @@ -0,0 +1,16 @@ +'use client'; + +import SelectBox from '../InputBox/SelectBox'; +function CommunityFilter() { + return ( +
    +

    100개

    + +
    + ); +} + +export default CommunityFilter; diff --git a/src/shared/components/community/CommunityHeader.tsx b/src/shared/components/community/CommunityHeader.tsx new file mode 100644 index 0000000..956376d --- /dev/null +++ b/src/shared/components/community/CommunityHeader.tsx @@ -0,0 +1,12 @@ +import PageHeader from '../pageHeader/PageHeader'; +import headerImg from '@/shared/assets/images/community_page_header.webp'; + +function CommunityHeader() { + return ( +
    + +
    + ); +} + +export default CommunityHeader; diff --git a/src/shared/components/community/CommunityTab.tsx b/src/shared/components/community/CommunityTab.tsx new file mode 100644 index 0000000..16fdf2a --- /dev/null +++ b/src/shared/components/community/CommunityTab.tsx @@ -0,0 +1,42 @@ +'use client'; + +import tw from '@/shared/utills/tw'; +import { useState } from 'react'; + +const tabItem = [ + { title: '전체' }, + { title: '레시피' }, + { title: '팁' }, + { title: '질문' }, + { title: '자유' }, +]; + +function CommunityTab() { + const [selectedIdx, setSelectedIdx] = useState(0); + + return ( +
    +
    +
    + {tabItem.map(({ title }, idx) => ( + + ))} +
    +
    +
    + ); +} + +export default CommunityTab; diff --git a/src/shared/components/community/PostCard.tsx b/src/shared/components/community/PostCard.tsx new file mode 100644 index 0000000..cc672e8 --- /dev/null +++ b/src/shared/components/community/PostCard.tsx @@ -0,0 +1,44 @@ +import Image from 'next/image'; +import prePost from '@/shared/assets/images/prepost_img.webp'; +import PostLabel from './PostLabel'; + +function PostCard({ label }: { label: string }) { + return ( +
    + + +
    +
    +

    칵테일 만들 때 준비물

    +
    +

    칵테일 처음 만들어 보는데 랄랄

    +

    가나다라마바사아자차카파타하

    +
    +
      +
    • 실버븬
    • + +
    • 3분 전
    • + +
    • 조회 3
    • + +
    • 댓글 3
    • +
    +
    +
    + 예비사진 +
    +
    +
    + ); +} + +export default PostCard; diff --git a/src/shared/components/community/PostLabel.tsx b/src/shared/components/community/PostLabel.tsx new file mode 100644 index 0000000..c838e52 --- /dev/null +++ b/src/shared/components/community/PostLabel.tsx @@ -0,0 +1,24 @@ +function PostLabel({ title }: { title: string }) { + return ( + + {title} + + ); +} + +export default PostLabel; diff --git a/src/shared/components/community/WriteBtn.tsx b/src/shared/components/community/WriteBtn.tsx new file mode 100644 index 0000000..26befbd --- /dev/null +++ b/src/shared/components/community/WriteBtn.tsx @@ -0,0 +1,12 @@ +import Write from '@/shared/assets/icons/edit_28.svg'; + +function WriteBtn() { + return ( + + ); +} + +export default WriteBtn; diff --git a/src/shared/components/header/DropdownMenu.tsx b/src/shared/components/header/DropdownMenu.tsx index 9fdc55b..65bbb2f 100644 --- a/src/shared/components/header/DropdownMenu.tsx +++ b/src/shared/components/header/DropdownMenu.tsx @@ -58,9 +58,9 @@ function DropdownMenu({ isClicked, setIsClicked }: Props) { }; return ( - -
    -
      - {navItem.map(({ label, href }, idx) => ( -
    • + {navItem.map(({ label, href }, idx) => ( +
    • + setIsClicked(false)} + className={`items-start ${pathname === href ? 'bg-tertiary/70 inline-flex pr-5 p-2 rounded-md text-secondary' : 'hover:text-black/70 flex'}`} + aria-current={pathname === href ? 'page' : undefined} > - setIsClicked(false)} - className={`items-start ${pathname === href ? 'bg-tertiary/70 inline-flex pr-5 p-2 rounded-md text-secondary' : 'hover:text-black/70 flex'}`} - aria-current={pathname === href ? 'page' : undefined} + {idx + 1}. + { + textRef.current[idx] = el; + }} + onMouseEnter={() => handleMouseEnter(idx)} + onMouseLeave={() => handleMouseLeave(idx)} > - {idx + 1}. - { - textRef.current[idx] = el; - }} - onMouseEnter={() => handleMouseEnter(idx)} - onMouseLeave={() => handleMouseLeave(idx)} - > - {label} - - -
    • - ))} -
    -
    -
    + {label} + + + + ))} +
+ +
-
+ +
-
+ ); } diff --git a/src/shared/components/index.tsx b/src/shared/components/index.tsx deleted file mode 100644 index defae75..0000000 --- a/src/shared/components/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -function index() { - return ; -} -export default index; diff --git a/src/shared/components/keep/Keep.tsx b/src/shared/components/keep/Keep.tsx index 2e9f49d..6d45f56 100644 --- a/src/shared/components/keep/Keep.tsx +++ b/src/shared/components/keep/Keep.tsx @@ -1,22 +1,20 @@ import KeepIcon from '@/shared/assets/icons/keep_36.svg'; import KeepIconActive from '@/shared/assets/icons/keep_active_36.svg'; - import { useState } from 'react'; -function Keep() { + +interface Props { + className?: string; +} + +function Keep({ className }: Props) { const [isClick, setIsClick] = useState(false); const handleClick = () => { setIsClick(!isClick); }; return ( - ); diff --git a/src/shared/components/pageHeader/PageHeader.tsx b/src/shared/components/pageHeader/PageHeader.tsx index e5f267a..0269ae1 100644 --- a/src/shared/components/pageHeader/PageHeader.tsx +++ b/src/shared/components/pageHeader/PageHeader.tsx @@ -10,7 +10,7 @@ interface Props { function PageHeader({ src, title, description }: Props) { return (
-

+

{title} -

-

+ +

{description}

diff --git a/src/shared/components/recipePage/cocktailCard/CocktailCard.tsx b/src/shared/components/recipePage/cocktailCard/CocktailCard.tsx new file mode 100644 index 0000000..d362703 --- /dev/null +++ b/src/shared/components/recipePage/cocktailCard/CocktailCard.tsx @@ -0,0 +1,29 @@ +import { StaticImageData } from 'next/image'; +import Image from 'next/image'; +import Img from '@/shared/assets/images/dummy/exampleCocktail.png'; +import Keep from '@/shared/components/keep/Keep'; + +interface Props { + src?: StaticImageData; + name?: string; + nameKo?: string; +} + +function CocktailCard({ src, name, nameKo }: Props) { + return ( +
  • +
    + {/* {name} */} + +
    + +
    +
    +
    +

    Old Fassioned

    +

    올드 패션드

    +
    +
  • + ); +} +export default CocktailCard; diff --git a/src/shared/components/recipePage/cocktailList/CocktailList.tsx b/src/shared/components/recipePage/cocktailList/CocktailList.tsx new file mode 100644 index 0000000..2c5ede9 --- /dev/null +++ b/src/shared/components/recipePage/cocktailList/CocktailList.tsx @@ -0,0 +1,34 @@ +'use client'; +import CocktailCard from '../cocktailCard/CocktailCard'; + +function CocktailList() { + // const [data,setData] = useState([]) + // useEffect(() => { + // fetch('http://localhost:8080/api/cocktails') + // .then((res) => res.json()) + // .then((data) => { + // console.log(data.data) + // setData(data.data) + // }); + // }, []); + + return ( +
      + {/* { + data.map(({ cocktailImgUrl, cocktailId, cocktailName }) => ( +
    • + +
    • + )) + } */} + + + + + + + +
    + ); +} +export default CocktailList; diff --git a/src/shared/styles/global.css b/src/shared/styles/global.css index dfee205..b4a696b 100644 --- a/src/shared/styles/global.css +++ b/src/shared/styles/global.css @@ -22,3 +22,8 @@ .scroll-down { transform: translateY(-100%); } + +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} diff --git a/src/shared/utills/scrollToTop.ts b/src/shared/utills/scrollToTop.ts deleted file mode 100644 index f960a4a..0000000 --- a/src/shared/utills/scrollToTop.ts +++ /dev/null @@ -1,11 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function throttle void>(func: F, delay: number) { - let lastCall = 0; - return function (...args: Parameters) { - const now = Date.now(); - if (now - lastCall >= delay) { - lastCall = now; - func(...args); - } - }; -} diff --git a/src/shared/utills/test.ts b/src/shared/utills/test.ts deleted file mode 100644 index 4f511a8..0000000 --- a/src/shared/utills/test.ts +++ /dev/null @@ -1,10 +0,0 @@ -// 테스트 입니다 - -export const test = () => { - console.log('test'); -}; - -// 수정입니다. -// 수정입니다.2 -// 수정입니다.3 -// 수정입니다.4