Defer heavy getServerSideProps on home, operator, addresses, and wallet to cut TTFB; centralize locale-safe formatters with client-only relative times; add compact ops UX, bridge/operator relay pagination, and Playwright route/scroll smoke in deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
'use client'
|
||
|
||
import { useId, useState, type ReactNode } from 'react'
|
||
|
||
interface DisclosureSectionProps {
|
||
title: string
|
||
children: ReactNode
|
||
defaultOpen?: boolean
|
||
className?: string
|
||
headingClassName?: string
|
||
/** When true, content is always visible and the toggle is hidden (desktop layout). */
|
||
forceOpen?: boolean
|
||
/** When true, keep accordion behavior on all breakpoints (no md:auto-expand). */
|
||
alwaysCollapsible?: boolean
|
||
}
|
||
|
||
export default function DisclosureSection({
|
||
title,
|
||
children,
|
||
defaultOpen = false,
|
||
className = '',
|
||
headingClassName = '',
|
||
forceOpen = false,
|
||
alwaysCollapsible = false,
|
||
}: DisclosureSectionProps) {
|
||
const [open, setOpen] = useState(defaultOpen || forceOpen)
|
||
const panelId = useId().replace(/:/g, '')
|
||
const isOpen = forceOpen || open
|
||
|
||
return (
|
||
<section className={className}>
|
||
{forceOpen ? (
|
||
<div className={`mb-3 text-sm font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400 ${headingClassName}`}>
|
||
{title}
|
||
</div>
|
||
) : (
|
||
<button
|
||
type="button"
|
||
aria-expanded={isOpen}
|
||
aria-controls={panelId}
|
||
onClick={() => setOpen((current) => !current)}
|
||
className={`flex w-full items-center justify-between gap-3 rounded-lg border border-gray-200 bg-gray-50/80 px-3 py-2.5 text-left text-sm font-semibold uppercase tracking-wide text-gray-700 transition hover:border-primary-300 dark:border-gray-800 dark:bg-gray-900/40 dark:text-gray-200 dark:hover:border-primary-700 ${alwaysCollapsible ? '' : 'md:hidden'} ${headingClassName}`}
|
||
>
|
||
<span>{title}</span>
|
||
<span aria-hidden="true" className="text-base text-gray-500 dark:text-gray-400">
|
||
{isOpen ? '−' : '+'}
|
||
</span>
|
||
</button>
|
||
)}
|
||
|
||
<div
|
||
id={panelId}
|
||
className={
|
||
forceOpen
|
||
? ''
|
||
: alwaysCollapsible
|
||
? `${isOpen ? 'block' : 'hidden'} mt-3`
|
||
: `${isOpen ? 'block' : 'hidden'} mt-3 md:mt-0 md:block`
|
||
}
|
||
>
|
||
{!forceOpen && !alwaysCollapsible ? (
|
||
<div className="mb-3 hidden text-sm font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400 md:block">
|
||
{title}
|
||
</div>
|
||
) : null}
|
||
{children}
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|