diff --git a/src/assets/icons/ic_analysis.svg b/src/assets/icons/ic_analysis.svg index c192043..2ef1d69 100644 --- a/src/assets/icons/ic_analysis.svg +++ b/src/assets/icons/ic_analysis.svg @@ -1,7 +1,7 @@ - - + + diff --git a/src/assets/icons/ic_calculate.svg b/src/assets/icons/ic_calculate.svg index 8f870f9..77c358f 100644 --- a/src/assets/icons/ic_calculate.svg +++ b/src/assets/icons/ic_calculate.svg @@ -1,9 +1,9 @@ - - - - + + + + diff --git a/src/assets/icons/ic_calendar.svg b/src/assets/icons/ic_calendar.svg index 59bfce9..6d6e376 100644 --- a/src/assets/icons/ic_calendar.svg +++ b/src/assets/icons/ic_calendar.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/src/assets/icons/ic_chat.svg b/src/assets/icons/ic_chat.svg new file mode 100644 index 0000000..42cf0a9 --- /dev/null +++ b/src/assets/icons/ic_chat.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/icons/ic_my.svg b/src/assets/icons/ic_my.svg index d3f6aa7..9c8b5ad 100644 --- a/src/assets/icons/ic_my.svg +++ b/src/assets/icons/ic_my.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/assets/icons/ic_settings.svg b/src/assets/icons/ic_settings.svg index 73cc61c..2d14619 100644 --- a/src/assets/icons/ic_settings.svg +++ b/src/assets/icons/ic_settings.svg @@ -1,7 +1,7 @@ - - + + diff --git a/src/assets/icons/symbol_logo.svg b/src/assets/icons/symbol_logo.svg new file mode 100644 index 0000000..f917e33 --- /dev/null +++ b/src/assets/icons/symbol_logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/components/Header/index.stories.tsx b/src/components/Header/index.stories.tsx new file mode 100644 index 0000000..026c5ec --- /dev/null +++ b/src/components/Header/index.stories.tsx @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from '@storybook/nextjs'; +import Header from './index'; + +const meta = { + title: 'Components/Header', + component: Header, + parameters: { + layout: 'fullscreen', + viewport: { + defaultViewport: 'mobile1', + }, + }, + tags: ['autodocs'], + args: { + onSettingsClick: () => { + console.log('Settings clicked'); + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; + +export const WithoutSettings: Story = { + args: { + showSettings: false, + }, +}; diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx new file mode 100644 index 0000000..ffac7f5 --- /dev/null +++ b/src/components/Header/index.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { SettingsIcon, SymbolLogoIcon } from '@/components/Icons/index'; + +interface HeaderProps { + showSettings?: boolean; + onSettingsClick?: () => void; +} + +function HeaderLogo() { + return ( +
+ +
+ ); +} + +function HeaderSettingsButton({ onClick }: { onClick?: () => void }) { + return ( + + ); +} + +export default function Header({ showSettings = true, onSettingsClick }: HeaderProps) { + return ( +
+
+ + {showSettings && } +
+
+ ); +} diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts new file mode 100644 index 0000000..701e07b --- /dev/null +++ b/src/components/Icons/index.ts @@ -0,0 +1,28 @@ +// Navigation Icons +export { default as CalendarIcon } from '@/assets/icons/ic_calendar.svg'; +export { default as AnalysisIcon } from '@/assets/icons/ic_analysis.svg'; +export { default as ChatIcon } from '@/assets/icons/ic_chat.svg'; +export { default as CalculateIcon } from '@/assets/icons/ic_calculate.svg'; +export { default as MyIcon } from '@/assets/icons/ic_my.svg'; + +// Action Icons +export { default as CheckIcon } from '@/assets/icons/ic_check.svg'; +export { default as ClosedIcon } from '@/assets/icons/ic_closed.svg'; +export { default as CopyIcon } from '@/assets/icons/ic_copy.svg'; +export { default as DownloadIcon } from '@/assets/icons/ic_download.svg'; +export { default as EditIcon } from '@/assets/icons/ic_edit.svg'; +export { default as InfoIcon } from '@/assets/icons/ic_info.svg'; + +// Arrow Icons +export { default as LeftArrowIcon } from '@/assets/icons/ic_leftarrow.svg'; +export { default as RightArrowIcon } from '@/assets/icons/ic_rightarrow.svg'; + +// Date Icons +export { default as DateIcon } from '@/assets/icons/ic_date.svg'; + +// Settings Icons +export { default as SettingsIcon } from '@/assets/icons/ic_settings.svg'; +export { default as SettingsArrowIcon } from '@/assets/icons/ic_settings_arrow.svg'; + +// Logo Icons +export { default as SymbolLogoIcon } from '@/assets/icons/symbol_logo.svg'; diff --git a/src/components/Navigation/index.stories.tsx b/src/components/Navigation/index.stories.tsx new file mode 100644 index 0000000..f085156 --- /dev/null +++ b/src/components/Navigation/index.stories.tsx @@ -0,0 +1,65 @@ +import type { Meta, StoryObj } from '@storybook/nextjs'; +import Navigation from './index'; + +const meta = { + title: 'Components/Navigation', + component: Navigation, + parameters: { + layout: 'fullscreen', + viewport: { + defaultViewport: 'mobile1', + }, + }, + tags: ['autodocs'], + args: { + onItemClick: (id: string) => { + console.log('Navigation item clicked:', id); + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + defaultActiveId: 'calendar', + }, +}; + +export const ActiveCalendar: Story = { + args: { + defaultActiveId: 'calendar', + }, +}; + +export const ActiveAnalysis: Story = { + args: { + defaultActiveId: 'analysis', + }, +}; + +export const ActiveChat: Story = { + args: { + defaultActiveId: 'chat', + }, +}; + +export const ActiveCalculator: Story = { + args: { + defaultActiveId: 'calculator', + }, +}; + +export const ActiveMy: Story = { + args: { + defaultActiveId: 'my', + }, +}; diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx new file mode 100644 index 0000000..0f507dc --- /dev/null +++ b/src/components/Navigation/index.tsx @@ -0,0 +1,138 @@ +'use client'; + +import { useState } from 'react'; +import { + CalendarIcon, + AnalysisIcon, + ChatIcon, + CalculateIcon, + MyIcon, +} from '@/components/Icons/index'; + +type NavigationItemId = 'calendar' | 'analysis' | 'chat' | 'calculator' | 'my'; + +interface NavigationItem { + id: NavigationItemId; + label: string; + icon: React.ComponentType>; +} + +interface NavigationProps { + defaultActiveId?: NavigationItemId; + onItemClick?: (id: NavigationItemId) => void; +} + +interface NavigationItemButtonProps { + item: NavigationItem; + isActive: boolean; + isCenter: boolean; + onClick: () => void; +} + +const NAVIGATION_ITEMS: NavigationItem[] = [ + { id: 'calendar', label: '가계부', icon: CalendarIcon }, + { id: 'analysis', label: '분석', icon: AnalysisIcon }, + { id: 'chat', label: '챗봇', icon: ChatIcon }, + { id: 'calculator', label: '정산', icon: CalculateIcon }, + { id: 'my', label: '마이', icon: MyIcon }, +]; + +const CENTER_BUTTON_ID: NavigationItemId = 'chat'; + +function getTextColorClass(isActive: boolean): string { + return isActive ? 'text-[var(--color-green-normal)]' : 'text-[var(--color-grey-light-active)]'; +} + +function getIconColorClass(isActive: boolean): string { + return isActive ? 'text-[var(--color-green-normal)]' : 'text-[var(--color-grey-light-active)]'; +} + +function getBaseButtonClasses(): string { + return 'flex flex-col items-center justify-center gap-1 transition-colors cursor-pointer'; +} + +function getChatButtonClasses(): string { + return 'flex h-11 w-11 items-center justify-center rounded-full bg-[var(--color-green-normal)] transition-colors hover:bg-[var(--color-green-normal-hover)] active:bg-[var(--color-green-normal-active)]'; +} + +function ChatNavigationButton({ item, onClick }: { item: NavigationItem; onClick: () => void }) { + const IconComponent = item.icon; + + return ( + + ); +} + +function RegularNavigationButton({ + item, + isActive, + onClick, +}: { + item: NavigationItem; + isActive: boolean; + onClick: () => void; +}) { + const IconComponent = item.icon; + const textColorClass = getTextColorClass(isActive); + const iconColorClass = getIconColorClass(isActive); + + return ( + + ); +} + +function NavigationItemButton({ item, isActive, isCenter, onClick }: NavigationItemButtonProps) { + if (isCenter) { + return ; + } + + return ; +} + +export default function Navigation({ defaultActiveId = 'calendar', onItemClick }: NavigationProps) { + const [activeId, setActiveId] = useState(defaultActiveId); + + const handleItemClick = (id: NavigationItemId) => { + setActiveId(id); + onItemClick?.(id); + }; + + const isCenterItem = (id: NavigationItemId): boolean => id === CENTER_BUTTON_ID; + + return ( + + ); +}