flutter
flutter测试内容
这是代码
"use client";
import { Post } from "@/interfaces/post";
import Link from "next/link";
import { useState } from "react";
import { useRouter } from "next/navigation";
import cn from "classnames";
type Props = {
posts: Post[];
tags: string[];
};
export function KnowledgeTree({ posts, tags }: Props) {
// Group posts by tag
const postsByTag = tags.reduce<Record<string, Post[]>>((acc, tag) => {
acc[tag] = posts.filter((post) => post.tags.includes(tag));
return acc;
}, {});
// State to track expanded tags
const [expandedTags, setExpandedTags] = useState<Record<string, boolean>>({});
const router = useRouter();
const toggleTag = (tag: string) => {
setExpandedTags((prev) => ({
...prev,
[tag]: !prev[tag],
}));
};
return (
<div className="bg-white dark:bg-slate-900 rounded-2xl p-6 shadow-sm border border-slate-200 dark:border-slate-800">
<h3 className="text-sm font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400 mb-4 flex items-center gap-2">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
/>
</svg>
Knowledge Base
</h3>
<div className="space-y-1">
<button
type="button"
onClick={() => router.push("/")}
className="w-full flex items-center gap-2 px-3 py-2 text-sm font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800 rounded-lg transition-colors cursor-pointer"
>
<svg className="w-4 h-4 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
All Articles
<span className="ml-auto text-xs text-slate-400">{posts.length}</span>
</button>
{tags.map((tag) => {
const tagPosts = postsByTag[tag];
if (!tagPosts?.length) return null;
const isExpanded = expandedTags[tag];
return (
<div key={tag} className="group/item">
<div className="w-full flex items-center gap-1 px-2 py-1 hover:bg-slate-50 dark:hover:bg-slate-800 rounded-lg transition-colors select-none">
<button
onClick={() => toggleTag(tag)}
className="p-1 hover:bg-slate-200 dark:hover:bg-slate-700 rounded transition-colors"
aria-label={`Toggle ${tag}`}
>
<svg
className={cn(
"w-4 h-4 text-slate-400 transition-transform duration-200",
{ "rotate-90": isExpanded }
)}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</button>
<Link
href={`/?tag=${encodeURIComponent(tag)}`}
className="flex-1 flex items-center justify-between py-1 px-1 cursor-pointer"
>
<span className={cn("text-sm font-medium text-slate-700 dark:text-slate-300", { "text-blue-600 dark:text-blue-400": isExpanded })}>
{tag}
</span>
<span className="text-xs text-slate-400 bg-slate-100 dark:bg-slate-800 px-2 py-0.5 rounded-full">
{tagPosts.length}
</span>
</Link>
</div>
{isExpanded && (
<div className="pl-4 ml-2 border-l border-slate-200 dark:border-slate-800 my-1 space-y-0.5 animate-in slide-in-from-top-2 duration-200">
{tagPosts.map((post) => (
<Link
key={post.slug}
href={`/posts/${post.slug}`}
className="block px-3 py-1.5 text-sm text-slate-600 dark:text-slate-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50/50 dark:hover:bg-slate-800/50 rounded-md transition-colors truncate"
title={post.title}
>
{post.title}
</Link>
))}
</div>
)}
</div>
);
})}
</div>
</div>
);
}