diff --git a/src/app/(routes)/(quiz)/page.tsx b/src/app/(routes)/(quiz)/page.tsx new file mode 100644 index 00000000..1e82d122 --- /dev/null +++ b/src/app/(routes)/(quiz)/page.tsx @@ -0,0 +1,5 @@ +import { Button } from '@/components/ui/button' + +export default function Quiz() { + return +} diff --git a/src/app/(routes)/layout.tsx b/src/app/(routes)/layout.tsx new file mode 100644 index 00000000..7cfc6599 --- /dev/null +++ b/src/app/(routes)/layout.tsx @@ -0,0 +1,18 @@ +import { LeftNavLayout } from '@/components/left-nav-layout' +import { Viewport } from 'next' +import { PropsWithChildren } from 'react' + +interface LayoutProps extends PropsWithChildren {} + +export const viewport: Viewport = { + initialScale: 1.0, + userScalable: false, + maximumScale: 1.0, + minimumScale: 1.0, +} + +const Layout = ({ children }: LayoutProps) => { + return {children} +} + +export default Layout diff --git a/src/app/(routes)/repository/page.tsx b/src/app/(routes)/repository/page.tsx new file mode 100644 index 00000000..afa24e7a --- /dev/null +++ b/src/app/(routes)/repository/page.tsx @@ -0,0 +1,3 @@ +export default function Repository() { + return
Repository
+} diff --git a/src/app/(routes)/review/page.tsx b/src/app/(routes)/review/page.tsx new file mode 100644 index 00000000..31ee268d --- /dev/null +++ b/src/app/(routes)/review/page.tsx @@ -0,0 +1,3 @@ +export default function Review() { + return
Review
+} diff --git a/src/app/signin/page.tsx b/src/app/(routes)/signin/page.tsx similarity index 100% rename from src/app/signin/page.tsx rename to src/app/(routes)/signin/page.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f6caad8b..c70c6527 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,7 +17,9 @@ export default function RootLayout({ }>) { return ( - {children} + +
{children}
+ ) } diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index c32119cd..00000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Button } from '@/components/ui/button' - -export default function Home() { - return ( -
- -
- ) -} diff --git a/src/components/left-nav-layout.tsx b/src/components/left-nav-layout.tsx new file mode 100644 index 00000000..5ed5ec94 --- /dev/null +++ b/src/components/left-nav-layout.tsx @@ -0,0 +1,257 @@ +'use client' + +import { cn } from '@/lib/utils' +import { useSelectedLayoutSegments } from 'next/navigation' +import { PropsWithChildren, ReactNode, useMemo } from 'react' +import { Button } from './ui/button' +import Link from 'next/link' + +interface IconProps { + isActive: boolean +} + +interface NavItem { + href: string + Icon: (props: IconProps) => ReactNode + title: string + segments: string[][] +} + +export const LeftNavLayout = ({ children }: PropsWithChildren) => { + const segments = useSelectedLayoutSegments() + const navItems: NavItem[] = useMemo( + () => [ + { + href: '/', + title: '파워업 퀴즈', + Icon: PowerUpIcon, + segments: [['(quiz)']], + }, + { + href: '/review', + title: '복습 체크', + Icon: ReviewCheckIcon, + segments: [['review']], + }, + { + href: '/repository', + title: '공부 창고', + Icon: StudyRepositoryIcon, + segments: [['repository']], + }, + ], + [], + ) + + const activeItem = useMemo(() => findActiveNav(navItems, segments), [navItems, segments]) + + return ( +
+ {activeItem && ( +
+
+
+ +
+
+ +
+
+ {navItems.map((item) => { + const { href, Icon, title } = item + const isActive = activeItem == item + return ( + + + {title} + + ) + })} +
+
+ )} + {children} +
+ ) +} + +interface SegmentsRecord { + segments: string[] + item: NavItem +} + +const descendingOrderOfSegments = (recordA: SegmentsRecord, recordB: SegmentsRecord): number => + recordB.segments.length - recordA.segments.length + +const getIsActiveNav = (currentSegments: string[]) => (record: SegmentsRecord) => + record.segments.every((seg, index) => currentSegments[index] === seg) + +const findActiveNav = (items: NavItem[], currentSegments: string[]): NavItem | undefined => { + const segments = items.reduce((result, item) => { + item.segments.forEach((segments) => { + result.push({ + segments, + item, + }) + }) + return result + }, []) + + const isActiveSegment = getIsActiveNav(currentSegments) + return segments.sort(descendingOrderOfSegments).find(isActiveSegment)?.item +} + +// TODO: Icon 컴포넌트 구현 +function PowerUpIcon({ isActive }: IconProps) { + return ( + + + + + + + + + + + + ) +} + +function ReviewCheckIcon({ isActive }: IconProps) { + return ( + + + + + + + + + + + + ) +} + +function StudyRepositoryIcon({ isActive }: IconProps) { + return ( + + + + + + + + + + + + ) +} + +function LogoIcon() { + return ( + + + + + + + + + + + + ) +} + +function PlusIcon() { + return ( + + + + + ) +}