mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 16:26:57 +08:00
Feature/faq (#546)
* feat(web): add FAQ page with search, sidebar, and i18n integration; update navigation and routes; include user feedback analysis docs (faq.md) * docs: add filled frontend PR template for FAQ feature (PR_FRONTEND_FAQ.md) * docs(web): add Contributing & Tasks FAQ category near top with guidance on using GitHub Projects and PR contribution standards * feat(web,api): dynamically embed GitHub Projects roadmap in FAQ via /api/roadmap and RoadmapWidget; add env vars for GitHub token/org/project * chore(docker): pass GitHub roadmap env vars into backend container * docs(web): update FAQ with fork-based PR workflow, yellow links to roadmap/task dashboard, and contribution incentives; remove dynamic roadmap embed\n\nchore(api,docker): remove /api/roadmap endpoint and related env wiring * chore: revert unintended changes (.env.example, api/server.go, docker-compose.yml); remove local-only files (PR_FRONTEND_FAQ.md, web/faq.md) from PR * feat: 添加对重置密码页面的路由支持
This commit is contained in:
83
web/src/components/faq/FAQSidebar.tsx
Normal file
83
web/src/components/faq/FAQSidebar.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { t, type Language } from '../../i18n/translations'
|
||||
import type { FAQCategory } from '../../data/faqData'
|
||||
|
||||
interface FAQSidebarProps {
|
||||
categories: FAQCategory[]
|
||||
activeItemId: string | null
|
||||
language: Language
|
||||
onItemClick: (categoryId: string, itemId: string) => void
|
||||
}
|
||||
|
||||
export function FAQSidebar({
|
||||
categories,
|
||||
activeItemId,
|
||||
language,
|
||||
onItemClick,
|
||||
}: FAQSidebarProps) {
|
||||
return (
|
||||
<nav
|
||||
className="sticky top-24 h-[calc(100vh-120px)] overflow-y-auto pr-4"
|
||||
style={{
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: '#2B3139 #1E2329',
|
||||
}}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{categories.map((category) => (
|
||||
<div key={category.id}>
|
||||
{/* Category Title */}
|
||||
<div className="flex items-center gap-2 mb-3 px-3">
|
||||
<category.icon className="w-5 h-5" style={{ color: '#F0B90B' }} />
|
||||
<h3
|
||||
className="text-sm font-bold uppercase tracking-wide"
|
||||
style={{ color: '#F0B90B' }}
|
||||
>
|
||||
{t(category.titleKey, language)}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Category Items */}
|
||||
<ul className="space-y-1">
|
||||
{category.items.map((item) => {
|
||||
const isActive = activeItemId === item.id
|
||||
return (
|
||||
<li key={item.id}>
|
||||
<button
|
||||
onClick={() => onItemClick(category.id, item.id)}
|
||||
className="w-full text-left px-3 py-2 rounded-lg text-sm transition-all"
|
||||
style={{
|
||||
background: isActive
|
||||
? 'rgba(240, 185, 11, 0.1)'
|
||||
: 'transparent',
|
||||
color: isActive ? '#F0B90B' : '#848E9C',
|
||||
borderLeft: isActive
|
||||
? '3px solid #F0B90B'
|
||||
: '3px solid transparent',
|
||||
paddingLeft: isActive ? '9px' : '12px',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isActive) {
|
||||
e.currentTarget.style.background =
|
||||
'rgba(240, 185, 11, 0.05)'
|
||||
e.currentTarget.style.color = '#EAECEF'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isActive) {
|
||||
e.currentTarget.style.background = 'transparent'
|
||||
e.currentTarget.style.color = '#848E9C'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t(item.questionKey, language)}
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user