(進階) 搭配 Nano Stores 完成「過濾器按鈕」
index.astro的 ALL 按鈕,改為FilterButton.tsx- 套用先前建立好的
techStore.js
1. 建立 FilterButton 元件
新增 src\components\FilterButton.tsx
import { useState } from 'react'
type Props = {
categories: string[]
}
const FilterButton = function ({ categories }: Props) {
const [isOpen, setIsOpen] = useState(false)
const [active, setActive] = useState('All')
const allCategories = ['All', ...categories]
const handleSelect = (filter: string) => {
setActive(filter)
setIsOpen(false)
}
return (
<div className="flex items-center gap-2 p-4 bg-white">
<div
className={`flex items-center rounded-2xl overflow-hidden transition-all duration-500 ease-in-out
${isOpen ? 'max-w-[600px] shadow-md' : 'max-w-[120px]'}
`}
>
<button
onClick={() => setIsOpen(!isOpen)}
className={`px-4 py-2 bg-indigo-600 text-white rounded-2xl
${
isOpen
? 'text-blue-600 bg-blue-50'
: 'text-gray-700 hover:bg-blue-100 hover:text-blue-600'
}
`}
>
<span className="flex items-center gap-1">{active}</span>
</button>
<div
className={`flex items-center pr-2 transition-opacity duration-300
${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}
`}
>
{allCategories.map((c) => (
<button
key={c}
onClick={() => handleSelect(c)}
className="px-3 py-2 text-sm text-gray-500 whitespace-nowrap hover:bg-blue-50 rounded-full transition-all"
>
{c}
</button>
))}
</div>
</div>
</div>
)
}
export default FilterButton
2. 使用 FilterButton 設定 techStore 的 categoryFilter
// FilterButton.tsx
import { useState } from 'react'
import { setCategory } from '../store/techStore'
type Props = {
categories: string[]
}
const FilterButton = function ({ categories }: Props) {
const [isOpen, setIsOpen] = useState(false)
const [active, setActive] = useState('All')
const allCategories = ['All', ...categories]
const handleSelect = (filter: string) => {
setActive(filter)
setIsOpen(false)
setCategory(filter) // 使用 setCategory
}
return (
....
)
}
export default FilterButton
3. TechList 取得及時 techStore 的 categoryFilter
// SearchBar.tsx
import type { CategoryMap, Tech, TechStatus } from '../type/tech'
import { useState } from 'react'
import { useStore } from '@nanostores/react'
import { searchQuery } from '../store/searchStore'
import { categoryFilter } from '../store/techStore'
type TechListProps = {
category: CategoryMap
}
const CategorySection = ({
status,
items,
}: {
status: string
items: Tech[]
}) => {
const [isOpen, setIsOpen] = useState(true)
return <section>...</section>
}
const TechList = function ({ category }: TechListProps) {
const $searchQuery = useStore(searchQuery)
const $categoryFilter = useStore(categoryFilter)
const allCategory = {
mastered: category.mastered.filter((item) =>
item.body?.includes($searchQuery)
),
learning: category.learning.filter((item) =>
item.body?.includes($searchQuery)
),
wishlist: category.wishlist.filter((item) =>
item.body?.includes($searchQuery)
),
}
const filterCategory: CategoryMap =
$categoryFilter == 'All'
? allCategory
: ({
[$categoryFilter]: allCategory[$categoryFilter as TechStatus],
} as CategoryMap)
return (
<div className="grid gap-8">
{Object.entries(filterCategory).map(([status, items]) => (
<CategorySection status={status} items={items} key={status} />
))}
</div>
)
}
export default TechList
4. index 渲染 FilterButton
src\pages\index.astro---
import { getCollection } from 'astro:content'
import type { CategoryMap } from '../type/tech'
import Layout from '../layouts/Layout.astro'
import SearchBar from '../components/SearchBar'
import TechList from '../components/TechList'
import FilterButton from '../components/FilterButton'
const allTechs = await getCollection('techs')
const category: CategoryMap = {
mastered: allTechs.filter((t) => t.data.status === 'mastered'),
learning: allTechs.filter((t) => t.data.status === 'learning'),
wishlist: allTechs.filter((t) => t.data.status === 'wishlist'),
}
const categoryKeys = Object.keys(category)
---
<Layout title="DevPulse">
<main class="max-w-4xl mx-auto p-8">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 mb-8">
My Tech Radar
</h1>
<FilterButton categories={categoryKeys} client:load />
<SearchBar client:load />
<TechList category={category} client:load />
</main>
</Layout>










