Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a221768
[style] ์Šคํ”ผ๋„ˆ ui ์™„๋ฃŒ
EunbinJung Sep 24, 2025
709b01d
[style]keep์ปดํฌ๋„ŒํŠธ
mtm-git1018 Sep 23, 2025
3fb2731
[style]keep์ปดํฌ๋„ŒํŠธ ์•„์ด์ฝ˜ ํŽธ์ง‘
mtm-git1018 Sep 24, 2025
7999e09
[style] ์Šคํ”ผ๋„ˆ ui ์™„๋ฃŒ
EunbinJung Sep 24, 2025
a1ce68f
[chore] ์ถฉ๋Œ ํ•ด๊ฒฐ
EunbinJung Sep 24, 2025
c39465f
[style] ์Šคํ”ผ๋„ˆ ui ์™„๋ฃŒ
EunbinJung Sep 24, 2025
f6bfa0a
[style]keep์ปดํฌ๋„ŒํŠธ
mtm-git1018 Sep 23, 2025
316e09f
[style]keep์ปดํฌ๋„ŒํŠธ ์•„์ด์ฝ˜ ํŽธ์ง‘
mtm-git1018 Sep 24, 2025
7af729c
[style] ์Šคํ”ผ๋„ˆ ui ์™„๋ฃŒ
EunbinJung Sep 24, 2025
6ab0033
[chore] ์ถฉ๋Œ ํ•ด๊ฒฐ
EunbinJung Sep 24, 2025
74687b6
[style]๋ ˆ์‹œํ”ผ ํŽ˜์ด์ง€ ์ œ์ž‘
mtm-git1018 Sep 24, 2025
5afa4aa
[style]pageHeader์ปดํฌ๋„ŒํŠธ
mtm-git1018 Sep 24, 2025
4ce69c0
[style]page header์ปดํฌ๋„ŒํŠธ
mtm-git1018 Sep 24, 2025
dd92856
[style] star_bg๋ณ€๊ฒฝ
mtm-git1018 Sep 24, 2025
f0c38ba
[style] ์ปค๋ฎค๋‹ˆํ‹ฐ ui ์ž‘์—…
EunbinJung Sep 24, 2025
88c253b
[fix] ์˜ค๋ฅ˜ ์ˆ˜์ •
EunbinJung Sep 24, 2025
11d2e7f
[style]๋ ˆ์‹œํ”ผํŽ˜์ด์ง€
mtm-git1018 Sep 24, 2025
e2e0f71
[style] ์ปค๋ฎค๋‹ˆํ‹ฐ ui
EunbinJung Sep 24, 2025
54fee17
[style] ์‹œ๋งจํ‹ฑ ํƒœ๊ทธ ์ ‘๊ทผ์„ฑ ์ถ”๊ฐ€
EunbinJung Sep 24, 2025
2d1aebb
[refactor] ์‹œ๋งจํ‹ฑํƒœ๊ทธ ์ˆ˜์ •
EunbinJung Sep 24, 2025
30acec5
[style] ์Šคํ”ผ๋„ˆ ui ์™„๋ฃŒ
EunbinJung Sep 24, 2025
41fd64c
[style]keep์ปดํฌ๋„ŒํŠธ
mtm-git1018 Sep 23, 2025
a4f29b2
[style]recipeํŽ˜์ด์ง€
mtm-git1018 Sep 24, 2025
ed752d8
[style]์นตํ…Œ์ผํŽ˜์ด์ง€
mtm-git1018 Sep 24, 2025
1469169
[style] ์Šคํ”ผ๋„ˆ ui ์™„๋ฃŒ
EunbinJung Sep 24, 2025
2a5036e
[style]keep์ปดํฌ๋„ŒํŠธ
mtm-git1018 Sep 23, 2025
980876a
[style]keep์ปดํฌ๋„ŒํŠธ ์•„์ด์ฝ˜ ํŽธ์ง‘
mtm-git1018 Sep 24, 2025
0586a3d
Merge branch 'dev' into style/community#18
EunbinJung Sep 24, 2025
c1fe944
Merge pull request #56 from prgrms-web-devcourse-final-project/style/โ€ฆ
EunbinJung Sep 24, 2025
2aae9c9
[style]๋ ˆ์‹œํ”ผํŽ˜์ด์ง€
mtm-git1018 Sep 24, 2025
d020d52
[style]recipeํŽ˜์ด์ง€
mtm-git1018 Sep 24, 2025
564150e
[style]์นตํ…Œ์ผํŽ˜์ด์ง€
mtm-git1018 Sep 24, 2025
668a558
[style]ํŽ˜์ด์ง€ ์ˆ˜์ •์‚ฌํ•ญ ์ˆ˜์ •
mtm-git1018 Sep 24, 2025
ea6ec02
Merge branch 'feat/cocktailPage#17' of https://github.com/prgrms-web-โ€ฆ
mtm-git1018 Sep 24, 2025
4bba1c8
[feat]selectBox์ปดํฌ๋„ŒํŠธ ๋‹ค์–‘ํ™”
mtm-git1018 Sep 25, 2025
2592864
[style] select๋ฐ•์Šค ์ •๋ ฌ
mtm-git1018 Sep 25, 2025
95961ba
[style] ์ˆ˜์ •์‚ฌํ•ญ ๋ฐ˜์˜
mtm-git1018 Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {

// TurboPack ์„ค์ •
experimental: {
turbo: {
Expand Down
4 changes: 0 additions & 4 deletions src/api/test.tsx

This file was deleted.

35 changes: 34 additions & 1 deletion src/app/community/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <div></div>;
return (
<main className="page-layout max-w-1024">
<div className="mt-3 mb-10 flex flex-col gap-8 ">
<section aria-labelledby="community-heading">
<h1 id="community-heading" className="sr-only">
์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜์ด์ง€
</h1>
<CommunityHeader />
</section>

<section
aria-label="ํƒญ๊ณผ ๊ธ€์“ฐ๊ธฐ"
className="flex justify-between item-center sm:flex-row flex-col gap-4 mt-1"
>
<CommunityTab />
<WriteBtn />
</section>

<section aria-label="๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก">
<CommunityFilter />
<PostCard label="๋ ˆ์‹œํ”ผ" />
<PostCard label="ํŒ" />
<PostCard label="์งˆ๋ฌธ" />
<PostCard label="์ž์œ " />
</section>
</div>
</main>
);
}
export default Page;
36 changes: 36 additions & 0 deletions src/app/recipe/components/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ul className="flex w-full gap-3">
{selectOption.map(({ id, option, title }) => {
return (
<li key={id}>
<SelectBox option={option} title={title} id={id} groupKey="filter" />
</li>
);
})}
</ul>
);
}
export default Accordion;
39 changes: 32 additions & 7 deletions src/app/recipe/page.tsx
Original file line number Diff line number Diff line change
@@ -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 | ์นตํ…Œ์ผ๋ ˆ์‹œํ”ผ',
Expand All @@ -10,12 +14,33 @@ export const metadata: Metadata = {
function Page() {
return (
<div className="w-full">
<PageHeader
src={Glass}
title="Cocktail Recipes"
description="๋‹ค์–‘ํ•˜๊ณ  ์žฌ๋ฐŒ๋Š” ์นตํ…Œ์ผ ๋ ˆ์‹œํ”ผ"
/>
<div className="page-layout max-w-1224"></div>
<section>
<PageHeader
src={Glass}
title="Cocktail Recipes"
description="๋‹ค์–‘ํ•˜๊ณ  ์žฌ๋ฐŒ๋Š” ์นตํ…Œ์ผ ๋ ˆ์‹œํ”ผ"
/>
</section>
<div className="page-layout max-w-1224 mt-6">
<section className="flex flex-col-reverse items-start gap-6 md:flex-row md:justify-between md:items-center ">
<Accordion />
<Input
placeholder="๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."
id="search"
variant="search"
className="w-full md:max-w-80"
/>
</section>
<section>
<div className="h-10 flex justify-between items-center mt-3 border-b-1 border-gray-light">
<p>n๊ฐœ</p>
<SelectBox option={['', '๋Œ“๊ธ€์ˆœ', '์ธ๊ธฐ์ˆœ']} title="์ตœ์‹ ์ˆœ" />
</div>
<section className="mt-5">
<CocktailList />
</section>
</section>
</div>
</div>
);
}
Expand Down
32 changes: 32 additions & 0 deletions src/shared/@store/accordionStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// zustand
import { create } from 'zustand';

// select๋ฐ•์Šค ์•„์ฝ”๋””์–ธ ๋ฉ”๋‰ด
export type ID = string | number;

type AccordionState = {
openByGroup: Record<string, ID | null>;
};

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<Accordion>((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 } })),
}));
1 change: 0 additions & 1 deletion src/shared/@store/store.ts

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/shared/assets/images/prepost_img.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/shared/assets/images/recipe_page_header.png
Binary file not shown.
Binary file added src/shared/assets/images/recipe_page_header.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 51 additions & 13 deletions src/shared/components/InputBox/SelectBox.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,82 @@
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<HTMLButtonElement | null>;
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>(() => 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 (
<div className="flex flex-col gap-2 relative h-6 w-30">
<div className="flex flex-col gap-2 relative h-6">
<button
ref={ref}
className="flex gap-2 cursor-pointer text-base"
onClick={() => setIsOpen(!isOpen)}
onClick={toggle}
aria-expanded={isOpen}
>
{select ? select : title}
{isOpen ? (
{localOpen ? (
<Down className="rotate-180 duration-300" />
) : (
<Down className="rotate-0 duration-300" />
)}
</button>

<ul
className={`w-30 bg-white text-gray-dark p-2 rounded-xl duration-200 absolute transition-all
${isOpen ? 'opacity-100 top-8' : 'opacity-0 pointer-events-none top-4'}`}
className={`w-30 bg-white text-gray-dark p-2 rounded-xl z-99 duration-200 absolute transition-all
${
groupKey
? localOpen
? 'opacity-100 top-8 left-0'
: 'opacity-0 pointer-events-none top-4 left-0'
: localOpen
? 'opacity-100 top-8 right-0'
: 'opacity-0 pointer-events-none top-4 right-0'
}`}
role="listbox"
>
{option.map((v, i) => (
Expand Down
16 changes: 16 additions & 0 deletions src/shared/components/community/CommunityFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

import SelectBox from '../InputBox/SelectBox';
function CommunityFilter() {
return (
<section
className="w-full flex justify-between items-center border-b-1 border-gray-light pb-1.5"
aria-label="์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ ฌ ํ•„ํ„ฐ"
>
<p aria-live="polite">100๊ฐœ</p>
<SelectBox option={['์ตœ์‹ ์ˆœ', '์ธ๊ธฐ์ˆœ', '๋Œ“๊ธ€์ˆœ']} title={'์ตœ์‹ ์ˆœ'} />
</section>
);
}

export default CommunityFilter;
12 changes: 12 additions & 0 deletions src/shared/components/community/CommunityHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import PageHeader from '../pageHeader/PageHeader';
import headerImg from '@/shared/assets/images/community_page_header.webp';

function CommunityHeader() {
return (
<section aria-label="์ปค๋ฎค๋‹ˆํ‹ฐ ํ—ค๋”">
<PageHeader src={headerImg} title="Community" description="์นตํ…Œ์ผ์— ๊ด€ํ•œ ๋ชจ๋“  ์ด์•ผ๊ธฐ" />
</section>
);
}

export default CommunityHeader;
42 changes: 42 additions & 0 deletions src/shared/components/community/CommunityTab.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<section className="relative sm:w-[70%] w-full" aria-label="์ปค๋ฎค๋‹ˆํ‹ฐ ํƒญ">
<div className="w-full overflow-x-scroll no-scrollbar scroll-smooth">
<div className="flex gap-3 w-max" aria-label="์ปค๋ฎค๋‹ˆํ‹ฐ ์นดํ…Œ๊ณ ๋ฆฌ">
{tabItem.map(({ title }, idx) => (
<button
key={title + idx}
role="tab"
aria-selected={selectedIdx === idx}
tabIndex={selectedIdx === idx ? 0 : -1}
onClick={() => setSelectedIdx(idx)}
className={tw(
`border-1 py-1 px-3 rounded-2xl transition-colors ease-in`,
selectedIdx === idx ? 'bg-secondary text-primary' : ''
)}
>
{title}
</button>
))}
</div>
</div>
</section>
);
}

export default CommunityTab;
44 changes: 44 additions & 0 deletions src/shared/components/community/PostCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<article className="pt-[30] pb-3 border-b-1 border-gray-light">
<PostLabel title={label} />

<section className="flex items-start justify-between mt-3 cursor-pointer" role="link">
<div className="flex flex-col gap-3">
<p className="font-bold sm:text-xl text-lg">์นตํ…Œ์ผ ๋งŒ๋“ค ๋•Œ ์ค€๋น„๋ฌผ</p>
<div className="font-light sm:text-[15px] text-sm">
<p>์นตํ…Œ์ผ ์ฒ˜์Œ ๋งŒ๋“ค์–ด ๋ณด๋Š”๋ฐ ๋ž„๋ž„</p>
<p>๊ฐ€๋‚˜๋‹ค๋ผ๋งˆ๋ฐ”์‚ฌ์•„์ž์ฐจ์นดํŒŒํƒ€ํ•˜</p>
</div>
<ul
className="flex font-light sm:gap-3 gap-1 sm:text-[14px] text-[12px] text-gray"
aria-label="๊ฒŒ์‹œ๊ธ€ ์ •๋ณด"
>
<li>์‹ค๋ฒ„๋ธฌ</li>
<li aria-hidden="true">|</li>
<li>3๋ถ„ ์ „</li>
<li aria-hidden="true">|</li>
<li>์กฐํšŒ 3</li>
<li aria-hidden="true">|</li>
<li>๋Œ“๊ธ€ 3</li>
</ul>
</div>
<figure className="flex items-start">
<Image
src={prePost}
alt="์˜ˆ๋น„์‚ฌ์ง„"
width={120}
height={120}
className="md:w-[120px] sm:w-[100px] w-[80px] self-start"
/>
</figure>
</section>
</article>
);
}

export default PostCard;
Loading