Repository: rak517/Epigram
Pull request #28: EP-14 공용 side-bar 컴포넌트 구현
Author: login: jaehyeongjung
Labels: name: Priority: Low 🐢
description: 우선순위 낮음
name: Type: Feature ✨
description: 기능 개발
Created: 2025-03-11T15:13:30Z | Merged: 2025-03-12T08:00:51Z

## ❓이슈
- close #8 

## :writing_hand: Description

공용 컴포넌트 side-bar 구현 

<!-- 어떤 내용의 PR인지 작성해주세요. (ex. 메인 페이지 레이아웃 작업) -->
<!-- ⚠️ PR에는 해당 PR의 제목에 해당하는 내용만 들어가 있어야 합니다!  -->

## :white_check_mark: Checklist

### PR

SideBar 임포트 후 다음과 같이 사용하면 됩니다. 
다음은 테스트 코드인데, 페이지에서는 조건부 렌더링 하면 될 듯 합니다. 

```
const TestPage = () => {
  const [selectedTab, setSelectedTab] = useState<'feed' | 'search'>('feed');

  // 탭 선택 시 상태 업데이트
  const handleTabSelect = (tab: 'feed' | 'search') => {
    setSelectedTab(tab);
    console.log(`선택된 탭: ${tab}`); // 콘솔에 출력하여 확인
  };

  return (
    <div>
      <h1>테스트 페이지</h1>
      <p>현재 선택된 탭: {selectedTab}</p>

      {/* 사이드바 컴포넌트에 onSelectedTab 전달 */}
      **<SideBar onSelectedTab={handleTabSelect} />**
    </div>
  );
```


https://github.com/user-attachments/assets/5823af10-c90a-49c1-bf9e-a60611ff3196



<!-- 작성중인 PR인 경우, Draft 모드로 생성해주세요. -->

- [x] Branch Convention 확인
  > `feat/*` 기능 구현, `fix/*` 버그 수정, `refactor/*` 개선
- [x] Base Branch 확인
- [x] 적절한 Label 지정
- [x] Assignee 및 Reviewer 지정

### Test

- [x] 로컬 작동 확인

### Additional Notes

<!-- 추가 사항이 있을 경우, Todo list를 작성해주세요. -->

- [x] (없음)


Linked issue #8: [EP-14] Feat: [공용] 사이드바 구현
### 🎟️ 상위 작업 (에픽 키)

EP-11

### 📝 상세 내용 (Description)

[공용] 사이드바 구현에 관한 이슈입니다.

### ✅ 체크리스트 (Tasks)

- [ ] 각 요소 별 클릭시 URL 이동 구현
- [ ] 사이드바 오픈 시 바탕 화면 어둡게 블러 처리


### 📄 참고 자료

<img width="151" alt="Image" src="https://github.com/user-attachments/assets/f981a4a8-2075-4830-831b-aa374e75b6d4" />

<img width="296" alt="Image" src="https://github.com/user-attachments/assets/018e32cd-9d90-4fb6-9ab4-5419d96bca8a" />

Diff:
diff --git a/src/assets/icons/X.svg b/src/assets/icons/X.svg
new file mode 100644
index 00000000..57122c43
--- /dev/null
+++ b/src/assets/icons/X.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M17 7L7 17" stroke="#373737" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M7 7L17 17" stroke="#373737" stroke-width="1.5" stroke-linecap="round"/>
+</svg>
diff --git a/src/assets/icons/sideBar.svg b/src/assets/icons/sideBar.svg
new file mode 100644
index 00000000..14a13ceb
--- /dev/null
+++ b/src/assets/icons/sideBar.svg
@@ -0,0 +1,5 @@
+<svg width="18" height="12" viewBox="0 0 18 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="18" height="2" rx="1" fill="#C4C4C4"/>
+<rect y="5" width="18" height="2" rx="1" fill="#C4C4C4"/>
+<rect y="10" width="18" height="2" rx="1" fill="#C4C4C4"/>
+</svg>
diff --git a/src/components/ui/keywordCard/index.tsx b/src/components/ui/keywordCard/index.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/ui/sideBar/index.tsx b/src/components/ui/sideBar/index.tsx
new file mode 100644
index 00000000..ea03a405
--- /dev/null
+++ b/src/components/ui/sideBar/index.tsx
@@ -0,0 +1,60 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import Image from 'next/image';
+import sideBarIcon from '@/assets/icons/sideBar.svg';
+import closeIcon from '@/assets/icons/X.svg';
+
+export interface SideBarProps {
+  onSelectedTab: (tab: 'feed' | 'search') => void;
+  isOpen?: boolean;
+}
+
+export default function SideBar({ onSelectedTab, isOpen = false }: SideBarProps) {
+  const [isopen, setIsOpen] = useState(isOpen);
+  const toggleSidebar = () => setIsOpen(!isopen);
+
+  const handleTabSelect = (tab: 'feed' | 'search') => {
+    onSelectedTab(tab); // 상위 컴포넌트로 선택된 탭 전달
+    setIsOpen(false); // 사이드바 닫기
+  };
+
+  useEffect(() => {
+    setIsOpen(isOpen);
+  }, [isOpen]);
+
+  return (
+    <React.Fragment>
+      {/* 햄버거 버튼 */}
+      <button className='flex h-6 w-6 items-center justify-center' onClick={toggleSidebar}>
+        <Image src={sideBarIcon} alt='sideBarIcon' width={18} height={12} />
+      </button>
+
+      {/* 오버레이 */}
+      {isopen && <div className='absolute inset-0' style={{ backgroundColor: 'rgba(0, 0, 0, 0.6)' }} onClick={toggleSidebar}></div>}
+
+      {/* 사이드바 */}
+      <motion.div
+        initial={{ x: '-100%' }} // 사이드바 시작 위치
+        animate={{ x: isopen ? 0 : '-100%' }} // 사이드바가 열리고 닫힐 때의 애니메이션
+        transition={{ type: 'spring', stiffness: 20 }}
+        className='fixed top-0 bottom-0 left-0 w-55 bg-blue-100 text-black'
+      >
+        <div className='w-55'>
+          <div className='flex h-12.5 w-full items-center justify-center border-b border-gray-100'>
+            <div className='flex h-6 w-47 justify-end'>
+              <Image src={closeIcon} alt='xIcon' width={24} height={24} onClick={toggleSidebar} />
+            </div>
+          </div>
+        </div>
+        <div className='h-37'>
+          <div className='ml-5 flex h-18.5 items-center' onClick={() => handleTabSelect('feed')}>
+            <p className='h-6.5 w-7'>피드</p>
+          </div>
+          <div className='ml-5 flex h-18.5 items-center' onClick={() => handleTabSelect('search')}>
+            <p className='h-6.5 w-7'>검색</p>
+          </div>
+        </div>
+      </motion.div>
+    </React.Fragment>
+  );
+}
diff --git a/src/stories/sideBar/index.stories.tsx b/src/stories/sideBar/index.stories.tsx
new file mode 100644
index 00000000..c16314db
--- /dev/null
+++ b/src/stories/sideBar/index.stories.tsx
@@ -0,0 +1,43 @@
+import type { Meta, StoryObj } from '@storybook/react';
+import SideBar from '../../components/ui/sideBar';
+import { action } from '@storybook/addon-actions';
+
+const meta: Meta<typeof SideBar> = {
+  title: 'Components/SideBar',
+  component: SideBar,
+  parameters: {
+    viewport: { defaultViewport: 'mobile1' },
+  },
+  argTypes: {
+    onSelectedTab: { 
+      action: 'onSelectedTab', // action 정의
+    },
+  },
+};
+
+export default meta;
+type Story = StoryObj<typeof SideBar>;
+
+export const Default: Story = {
+  args: {
+    onSelectedTab: (tab) => {
+      console.log(`Selected Tab: ${tab}`);
+      action('onSelectedTab')(tab);
+    },
+    isOpen: false,
+  },
+};
+
+export const Open: Story = {
+  args: {
+    onSelectedTab: action('onSelectedTab'),
+    isOpen: true,
+  },
+};
+
+export const Closed: Story = {
+  args: {
+    onSelectedTab: action('onSelectedTab'),
+    isOpen: false,
+  },
+};


Reviews:
user: login: rak517
body: 보완해서 다시 올려주시면 감사합니당
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-12T05:42:06Z
user: login: jaehyeongjung
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T07:22:51Z
user: login: jaehyeongjung
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T07:23:00Z
user: login: jaehyeongjung
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T07:23:08Z
user: login: rak517
state: APPROVED
author_association: OWNER
submitted_at: 2025-03-12T07:33:52Z
user: login: CJewon
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-12T07:43:54Z

Review comments:
path: public/sideBar.svg
user: login: rak517
body: 이미지 파일은 src/assets 경로에 폴더 icons, images로 분리해놨습니다. 
거기로 옮겨주시면 될 거 같아요!
created_at: 2025-03-12T02:54:49Z
author_association: OWNER
path: src/app/page.tsx
user: login: rak517
body: page.tsx는 원래대로 돌려서 올려주시면 감사합니다
```typescript
export default function Home() {
  return <div>landing Page</div>;
}
``` 
created_at: 2025-03-12T02:56:07Z
author_association: OWNER
path: src/components/ui/sideBar/index.stories.tsx
user: login: rak517
body: 스토리 파일은 src/stories 폴더 만들어 두었습니다 거기에 Sidebar 폴더 만드시고 
index.stories.tsx 파일 옮기시면 될 거 같아요! 그리고 아직 스토리가 다 작성이 안된거 같아서 확인 부탁드립니다
created_at: 2025-03-12T02:57:42Z
author_association: OWNER
path: public/sideBar.svg
user: login: jaehyeongjung
body: 넵 이동시켰습니다.
created_at: 2025-03-12T07:22:51Z
author_association: COLLABORATOR
path: src/app/page.tsx
user: login: jaehyeongjung
body: 넵 돌렸습니다
created_at: 2025-03-12T07:23:00Z
author_association: COLLABORATOR
path: src/components/ui/sideBar/index.stories.tsx
user: login: jaehyeongjung
body: 다시 작성해서 올렸습니다!
created_at: 2025-03-12T07:23:08Z
author_association: COLLABORATOR

PR comments:
user: login: vercel[bot]
created_at: 2025-03-11T15:13:36Z
body: [vc]: #iP67zmsr3Zk+CaQtIHqKpgceTCASP6iHBgdnaDZ8kq8=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJlcGlncmFtIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImVwaWdyYW0tZ2l0LWZlYXQtZXAtMTRzaWRlLWJhci1yYWs1MTdzLXByb2plY3RzLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3JhazUxN3MtcHJvamVjdHMvZXBpZ3JhbS81OXM3THd0RTNyTU5aS0JUWlJ0cHB1Skh6eWNVIiwicHJldmlld1VybCI6ImVwaWdyYW0tZ2l0LWZlYXQtZXAtMTRzaWRlLWJhci1yYWs1MTdzLXByb2plY3RzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJyb290RGlyZWN0b3J5IjpudWxsfV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **epigram** | ✅ Ready ([Inspect](https://vercel.com/rak517s-projects/epigram/59s7LwtE3rMNZKBTZRtppuJHzycU)) | [Visit Preview](https://epigram-git-feat-ep-14side-bar-rak517s-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/epigram-git-feat-ep-14side-bar-rak517s-projects.vercel.app?via=pr-comment-feedback-link) | Mar 12, 2025 7:58am |


author_association: NONE
user: login: jaehyeongjung
created_at: 2025-03-11T15:17:25Z
body: Rebase 때문에 다시 했다가 로컬에서는 정상 build 되는데
Action에서 자꾸 build 오류나서 새롭게 팠습니다 ㅜ
author_association: COLLABORATOR
user: login: CJewon
created_at: 2025-03-12T07:43:33Z
body: 수정 후 확인해본결과 특별히 수정해야할점은 없었습니다 고생하셨습니다 !
author_association: COLLABORATOR