Open6
Next.js(AppRouter)で、PathによってActive状態が変わるボトムナビゲーションバーの作成

お題
現在のPathのURLを受け取り、ボトムナビゲーションバーのActive状態を変更できるようにしたい
Pathを受け取るのには、usePathnameというhooksがある

第一段階
'use client'
import { FC } from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
type Item = {
path: string
label: string
}
type BottomNavbarProps = {
items: Item[]
path: string
}
const BottomNavbar: FC<BottomNavbarProps> = ({ items, path }) => {
return (
<ul>
{items.map((item) => {
const isActive = item.path === path
return (
<li key={item.path} className={isActive ? 'font-bold' : ''}>
<Link href={'/sample/blog/${item.path}'}>{item.label}</Link>
</li>
)
})}
</ul>
)
}
export const BottomNavbarContainer = () => {
const path = usePathname()
const items: Item[] = [
{ path: '/', label: 'Home' },
{ path: '/shop/search', label: 'PostCreate' },
{ path: '/profile', label: 'Profile' }
]
return <BottomNavbar items={items} path={path} />
}
アイコンとpathを渡してpropsとして渡す
iconのpathと現在のurlのpathが一致していたら、active状態になる

StorybookでNavbarを見るとこんな感じ
Pathと一致するとStyleが変わる
import { StoryObj, Meta } from '@storybook/react'
import { BottomNavbar } from './BottomNavbar'
const meta: Meta<typeof BottomNavbar> = {
component: BottomNavbar,
argTypes: {
items: {
control: {
type: 'object'
}
}
}
}
const items = [
{ label: 'Home', path: '/' },
{ label: 'Post', path: 'shop/search' },
{ label: 'Profile', path: 'profilet' }
]
export default meta
type Story = StoryObj<typeof BottomNavbar>
export const Default: Story = {
args: {
items
}
}

Styleを当てた
コード
export const BottomNavbar: FC<BottomNavbarProps> = ({ items, path }) => {
return (
<div className="md:disabled: fixed bottom-0 left-0 z-50 flex h-16 w-full items-center justify-center border bg-white">
<ul className="relative flex w-full justify-between px-4">
{items.map((item) => {
const isActive = item.path === path
return (
<li
key={item.path}
className={cn('flex flex-col items-center justify-center text-center mx-4', {
'font-bold': isActive
})}
>
<Link href={item.path} className="flex flex-col items-center justify-center text-sm ">
<span
className={cn('text-3xl text-text transition-transform transform', {
'font-extrabold text-red scale-110': isActive // アクティブな状態の色変更、サイズ拡大
})}
>
{item.icon}
</span>
<span
className={cn({
'border-b-2 border-red': isActive // アクティブな状態の下線
})}
>
{item.label}
</span>
</Link>
</li>
)
})}
</ul>
</div>
)
}

これだと、一つのpathに対応できない
例:shop/search -> post/create に遷移する場合

解決策1
対応するpathを配列上で受け取るようにする
'use client'
import React, { FC } from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { AiOutlineHome } from 'react-icons/ai'
import { AiOutlineUser } from 'react-icons/ai'
import { PiNotePencil } from 'react-icons/pi'
import { cn } from '@/libs/tailwind/utils'
type NavbarItem = {
paths: string[] //配列にする
label: string
icon: React.ReactNode
}
type BottomNavbarProps = {
items: NavbarItem[]
path: string
}
export const BottomNavbar: FC<BottomNavbarProps> = ({ items, path }) => {
return (
<div className="md:disabled: fixed bottom-0 left-0 z-50 flex h-20 w-full items-center justify-center border bg-white">
<ul className="relative flex w-full justify-between px-4">
{items.map((item) => {
const isActive = item.paths.includes(path)
return (
<li
key={item.paths[0]}
className={cn('flex flex-col items-center justify-center text-center mx-4 ', {
'font-bold': isActive
})}
>
<Link href={item.paths[0]} className="flex flex-col items-center justify-center text-sm ">
{/* アイコン */}
<span
className={cn('text-3xl text-text transition-transform transform', {
'font-extrabold text-red scale-110': isActive
})}
>
{item.icon}
</span>
{/* ラベル */}
<span
className={cn({
'border-b-2': isActive // アクティブな状態の下線
})}
>
{item.label}
</span>
</Link>
</li>
)
})}
</ul>
</div>
)
}
export const BottomNavbarContainer = () => {
const path = usePathname()
const items: NavbarItem[] = [
{ paths: ['/'], label: 'Home', icon: <AiOutlineHome /> },
{ paths: ['/shop/search', '/post/create'], label: 'Post', icon: <PiNotePencil /> }, //対応するpathを配列上で用意する
{ paths: ['/profile'], label: 'Profile', icon: <AiOutlineUser /> }
]
return <BottomNavbar items={items} path={path} />
}
応急処置感がすごい
❌これだとpathが増えた時にいちいち増やさないといけない