Files
sim/apps/docs/components/ui/faq.tsx
T
Waleed dcaa118752 improvement(docs): restructure sidebar, align chrome, rename Mothership to Chat (#6296)
Sidebar: 11 separator groups become 5, with each module a collapsible folder
that auto-opens on the active page. 61 always-visible rows drop to 16. Groups
mirror the app's own nav (Chats/Workspace/Workflows) rather than inventing a
taxonomy; Enterprise and Self-Hosting are hoisted out of Platform.

Chrome: register the `hover-hover` variant, without which every @sim/emcn hover
state silently compiled to nothing; restore the sidebar's Geist font stack; add
11 emcn tokens that were falling back to currentColor; adopt the named type
scale; align row geometry, hover tokens and group labels with the app.

Rename: mothership/ -> chat/ with redirects for the old URLs. Asset paths,
the @mothership.sim.ai domain and the `mothership` log-trigger enum value are
deliberately left alone -- they are CDN objects, a real domain, and a live
product value.

Also removes the page-type badge, drops the "Next" heading from the ToC, and
lets FAQ rows open independently so expanding one no longer shifts the page.
2026-08-05 13:49:17 -07:00

114 lines
3.1 KiB
TypeScript

'use client'
import { useState } from 'react'
import { ChevronRight } from '@sim/emcn/icons'
import { serializeJsonLd } from '@/lib/json-ld'
import { cn } from '@/lib/utils'
interface FAQItem {
question: string
answer: string
}
interface FAQProps {
items: FAQItem[]
title?: string
}
function FAQItemRow({
item,
isOpen,
onToggle,
}: {
item: FAQItem
isOpen: boolean
onToggle: () => void
}) {
return (
<div>
<button
type='button'
onClick={onToggle}
aria-expanded={isOpen}
className='flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-left font-medium text-[var(--text-body)] text-sm transition-colors hover:bg-[var(--surface-active)]'
>
<ChevronRight
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] transition-transform duration-200',
isOpen && 'rotate-90'
)}
/>
{item.question}
</button>
<div
className='grid transition-[grid-template-rows,opacity] duration-200 ease-in-out'
style={{
gridTemplateRows: isOpen ? '1fr' : '0fr',
opacity: isOpen ? 1 : 0,
}}
>
<div className='overflow-hidden'>
<div className='px-4 pt-2 pb-2.5 pl-11 text-[var(--text-secondary)] text-sm leading-relaxed'>
{item.answer}
</div>
</div>
</div>
</div>
)
}
export function FAQ({ items, title = 'Common Questions' }: FAQProps) {
/**
* Rows open independently rather than as a single-open accordion. Auto-closing
* a sibling collapses content *above* the row being opened, which yanks that
* row out from under the pointer — and since the FAQ is the last block on the
* page, the shrinking document also clamps the scroll position and lurches the
* whole viewport. Opening a row now only ever pushes content below it down.
*/
const [openIndices, setOpenIndices] = useState<ReadonlySet<number>>(() => new Set())
const toggle = (index: number) =>
setOpenIndices((previous) => {
const next = new Set(previous)
if (!next.delete(index)) next.add(index)
return next
})
const faqSchema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: items.map((item) => ({
'@type': 'Question',
name: item.question,
acceptedAnswer: {
'@type': 'Answer',
text: item.answer,
},
})),
}
return (
<div className='mt-12'>
<script
type='application/ld+json'
dangerouslySetInnerHTML={{ __html: serializeJsonLd(faqSchema) }}
/>
<h2 className='mb-4 font-medium text-xl'>{title}</h2>
<div className='border-[var(--border)] border-t border-b'>
{items.map((item, index) => (
<div
key={item.question}
className={cn(index !== items.length - 1 && 'border-[var(--border)] border-b')}
>
<FAQItemRow
item={item}
isOpen={openIndices.has(index)}
onToggle={() => toggle(index)}
/>
</div>
))}
</div>
</div>
)
}