diff --git a/apps/client/src/layout/Layout.tsx b/apps/client/src/layout/Layout.tsx index 1ce2359a..0b9a39b3 100644 --- a/apps/client/src/layout/Layout.tsx +++ b/apps/client/src/layout/Layout.tsx @@ -4,11 +4,12 @@ import { Sidebar } from '../shared/components/sidebar/Sidebar'; const Layout = () => { return ( <> - {/* TODO: 필요시 레이아웃 추가 */} - {/* TODO: 사이드바 추가 */} - +
- +
+ +
+
); }; diff --git a/apps/client/src/pages/level/Level.tsx b/apps/client/src/pages/level/Level.tsx index f627dfeb..77debd4d 100644 --- a/apps/client/src/pages/level/Level.tsx +++ b/apps/client/src/pages/level/Level.tsx @@ -42,7 +42,7 @@ export default function Level() { - +
diff --git a/apps/client/src/pages/myBookmark/MyBookmark.tsx b/apps/client/src/pages/myBookmark/MyBookmark.tsx index eb262445..a3e1ba7c 100644 --- a/apps/client/src/pages/myBookmark/MyBookmark.tsx +++ b/apps/client/src/pages/myBookmark/MyBookmark.tsx @@ -1,5 +1,47 @@ +import { REMIND_MOCK_DATA } from "@pages/remind/constants"; +import { Badge, Card } from "@pinback/design-system/ui"; +import { useState } from "react"; + const MyBookmark = () => { - return
MyBookmark
; + const [activeBadge, setActiveBadge] = useState('all'); + + const handleBadgeClick = (badgeType: string) => { + setActiveBadge(badgeType); + }; + + return ( +
+

나의 북마크

+
+ handleBadgeClick('all')} + isActive={activeBadge === 'all'} + /> + handleBadgeClick('notRead')} + isActive={activeBadge === 'notRead'} + /> +
+ +
+ {/* TODO: API 연결 후 수정 */} + {REMIND_MOCK_DATA.map((data) => ( + + ))} +
+
+ ); }; export default MyBookmark; diff --git a/apps/client/src/pages/remind/Remind.tsx b/apps/client/src/pages/remind/Remind.tsx index ed39e0e9..3aff4818 100644 --- a/apps/client/src/pages/remind/Remind.tsx +++ b/apps/client/src/pages/remind/Remind.tsx @@ -1,5 +1,47 @@ +import { Badge, Card } from '@pinback/design-system/ui'; +import { useState } from 'react'; +import { REMIND_MOCK_DATA } from './constants'; + const Remind = () => { - return
Remind
; + const [activeBadge, setActiveBadge] = useState('notRead'); + + const handleBadgeClick = (badgeType: string) => { + setActiveBadge(badgeType); + }; + + return ( +
+

리마인드

+
+ handleBadgeClick('notRead')} + isActive={activeBadge === 'notRead'} + /> + handleBadgeClick('read')} + isActive={activeBadge === 'read'} + /> +
+ +
+ {/* TODO: API 연결 후 수정 */} + {REMIND_MOCK_DATA.map((data) => ( + + ))} +
+
+ ); }; export default Remind; diff --git a/apps/client/src/pages/remind/constants/index.ts b/apps/client/src/pages/remind/constants/index.ts new file mode 100644 index 00000000..8850f894 --- /dev/null +++ b/apps/client/src/pages/remind/constants/index.ts @@ -0,0 +1,114 @@ +export const REMIND_MOCK_DATA = [ + { + id: 1, + title: '리액트 쿼리 강의 듣기', + content: '1강부터 5강까지 완강하고 복습하기', + timeRemaining: '1시간 30분', + category: '공부', + }, + { + id: 2, + title: '장보기', + content: '우유, 계란, 채소 구매', + timeRemaining: '3시간 00분', + category: '생활', + }, + { + id: 3, + title: '프로젝트 기획서 초안 작성', + content: '주요 기능 및 화면 흐름 정리', + timeRemaining: '완료', + category: '업무', + }, + { + id: 4, + title: '헬스장 가기', + content: '하체 운동 루틴 진행', + timeRemaining: '5시간 10분', + category: '운동', + }, + { + id: 5, + title: '주간 회고 작성', + content: '이번 주에 배운 점과 개선할 점 정리', + timeRemaining: '완료', + category: '자기계발', + }, + { + id: 6, + title: '저녁 약속', + content: '강남역 2번 출구에서 친구 만나기', + timeRemaining: '8시간 00분', + category: '약속', + }, + { + id: 7, + title: '알고리즘 문제 풀기', + content: '백준 골드 문제 2개 풀기', + timeRemaining: '2시간 45분', + category: '공부', + }, + { + id: 8, + title: '이메일 회신', + content: 'A사에서 온 제휴 문의 메일 확인하고 회신', + timeRemaining: '완료', + category: '업무', + }, + { + id: 9, + title: '방 청소하기', + content: '책상 정리 및 바닥 청소기 돌리기', + timeRemaining: '0시간 50분', + category: '생활', + }, + { + id: 10, + title: 'TypeScript 스터디 준비', + content: '제네릭 파트 발표 자료 만들기', + timeRemaining: '10시간 00분', + category: '공부', + }, + { + id: 11, + title: '은행 업무 보기', + content: '만기 된 예금 재예치하기', + timeRemaining: '완료', + category: '금융', + }, + { + id: 12, + title: '블로그 글 작성', + content: '이번 주에 해결한 기술적 문제에 대해 포스팅하기', + timeRemaining: '1일 2시간', + category: '자기계발', + }, + { + id: 13, + title: '가족과 통화하기', + content: '부모님께 안부 전화드리기', + timeRemaining: '4시간 20분', + category: '약속', + }, + { + id: 14, + title: '주말 산책 계획', + content: '가까운 공원 산책 코스 찾아보기', + timeRemaining: '2일 0시간', + category: '여가', + }, + { + id: 15, + title: '팀 회의록 정리', + content: '어제 진행된 스프린트 계획 회의 내용 정리해서 공유', + timeRemaining: '완료', + category: '업무', + }, + { + id: 16, + title: '팀 회의록 정리', + content: '어제 진행된 스프린트 계획 회의 내용 정리해서 공유', + timeRemaining: '완료', + category: '업무', + }, +]; diff --git a/packages/design-system/src/components/badge/Badge.stories.tsx b/packages/design-system/src/components/badge/Badge.stories.tsx index f005d292..d9a17129 100644 --- a/packages/design-system/src/components/badge/Badge.stories.tsx +++ b/packages/design-system/src/components/badge/Badge.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { within, userEvent } from '@storybook/test'; +import { within, userEvent, fn } from '@storybook/test'; import Badge from './Badge'; +import { useState } from 'react'; const meta: Meta = { title: 'Components/Badge', @@ -9,6 +10,8 @@ const meta: Meta = { args: { text: '알림', countNum: 3, + onClick: () => alert('onClick 실행'), + isActive: true, }, argTypes: { text: { control: 'text', description: '뱃지 라벨 텍스트' }, @@ -16,6 +19,18 @@ const meta: Meta = { control: { type: 'number', min: 0 }, description: '카운트 숫자(옵션)', }, + + isActive: { + control: 'boolean', + description: '활성화 여부', + table: { + type: { summary: 'boolean' }, + }, + }, + onClick: { + action: 'clicked', + description: '뱃지 클릭 시 호출되는 콜백 함수(옵션)', + }, }, parameters: { docs: { @@ -33,22 +48,44 @@ type Story = StoryObj; export const Default: Story = {}; export const NoCount: Story = { - args: { text: '카운트 없음', countNum: undefined }, + args: { text: '카운트 없음', countNum: 0, isActive: false }, }; export const LargeCount: Story = { args: { text: '메시지', countNum: 12000 }, }; -export const Clicked: Story = { - args: { text: '클릭해줘', countNum: 5 }, +export const ActiveBadge: Story = { + render: (args) => { + const ClickedBadge = () => { + const [isActive, setIsActive] = useState(false); + + return ( + { + setIsActive((prev) => !prev); // 토글 + args.onClick?.(); // 액션 로그 + }} + /> + ); + }; + + return ; + }, + args: { + text: '클릭해줘', + countNum: 5, + onClick: fn(), + }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); await userEvent.click(await canvas.findByText(String(args.text))); }, parameters: { docs: { - description: { story: '로드 직후 자동 클릭으로 활성 상태 미리보기.' }, + description: { story: 'onClick으로 뱃지 활/비활성화' }, }, }, }; diff --git a/packages/design-system/src/components/badge/Badge.tsx b/packages/design-system/src/components/badge/Badge.tsx index 9418c6c6..54442b4b 100644 --- a/packages/design-system/src/components/badge/Badge.tsx +++ b/packages/design-system/src/components/badge/Badge.tsx @@ -1,43 +1,50 @@ import { cva } from 'class-variance-authority'; -import { useState } from 'react'; export interface BadgeProps { text: string; countNum?: number; + isActive: boolean; + onClick?: () => void; } + const BadgeTxtStyleVariants = cva('sub3-b', { variants: { - click: { + active: { true: 'text-font-black-1', false: 'text-font-ltgray-4', } as const, }, defaultVariants: { - click: false, + active: false, }, }); + const BadgeStyleVariants = cva( 'text-white-bg sub5-sb rounded-[0.4rem] px-[0.8rem] py-[0.4rem]', { variants: { - click: { + active: { true: 'bg-main500', false: 'bg-gray300', } as const, }, defaultVariants: { - click: false, + active: false, }, } ); -const Badge = ({ text, countNum }: BadgeProps) => { - const [isClick, setIsClick] = useState(false); + +const Badge = ({ text, countNum, isActive, onClick }: BadgeProps) => { return (
setIsClick(true)} + onClick={onClick} > - {text} - {countNum} + + {text} + + + {countNum} +
); }; diff --git a/packages/tailwind-config/shared-styles.css b/packages/tailwind-config/shared-styles.css index 2d2dfd8d..03e77c0a 100644 --- a/packages/tailwind-config/shared-styles.css +++ b/packages/tailwind-config/shared-styles.css @@ -158,6 +158,16 @@ box-shadow: 0px 0px 32px 0px rgba(0, 0, 0, 0.1); } +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +/* 기타 브라우저용 (IE, Edge 구버전, Firefox) */ +.scrollbar-hide { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + @theme { --color-*: initial;