11import type React from 'react'
2+ import { Children , cloneElement , isValidElement } from 'react'
23import { highlight } from 'fumadocs-core/highlight'
34import { findNeighbour } from 'fumadocs-core/page-tree'
45import type { ApiPageProps } from 'fumadocs-openapi/ui'
56import { createAPIPage } from 'fumadocs-openapi/ui'
67import { Pre } from 'fumadocs-ui/components/codeblock'
78import defaultMdxComponents from 'fumadocs-ui/mdx'
8- import { DocsBody , DocsPage , DocsTitle } from 'fumadocs-ui/page'
9+ import { DocsBody , DocsPage } from 'fumadocs-ui/page'
910import { notFound } from 'next/navigation'
1011import { PageFooter } from '@/components/docs-layout/page-footer'
12+ import { PageHeader } from '@/components/docs-layout/page-header'
1113import { PageNavigationArrows } from '@/components/docs-layout/page-navigation-arrows'
1214import { LLMCopyButton } from '@/components/page-actions'
1315import { StructuredData } from '@/components/structured-data'
@@ -51,6 +53,24 @@ async function ApiCodeBlock({ lang, code }: { lang: string; code: string }) {
5153 )
5254}
5355
56+ interface ApiSlotElementProps extends React . HTMLAttributes < HTMLElement > {
57+ items ?: unknown [ ]
58+ }
59+
60+ /** Labels Fumadocs auth selectors while retaining their selection state and content. */
61+ function labelAuthSelectors ( node : React . ReactNode ) : React . ReactNode {
62+ if ( ! isValidElement < ApiSlotElementProps > ( node ) ) return node
63+
64+ const props = node . props
65+ const children =
66+ props . children === undefined ? undefined : Children . map ( props . children , labelAuthSelectors )
67+
68+ if ( Array . isArray ( props . items ) ) {
69+ return cloneElement ( node , { 'aria-label' : 'Authentication method' } , children )
70+ }
71+ return children === undefined ? node : cloneElement ( node , undefined , children )
72+ }
73+
5474const APIPage = createAPIPage ( openapi , {
5575 renderCodeBlock : ( props ) => < ApiCodeBlock { ...props } /> ,
5676 playground : { enabled : false } ,
@@ -68,12 +88,14 @@ const APIPage = createAPIPage(openapi, {
6888 content : {
6989 renderOperationLayout : ( slots ) => {
7090 return (
71- < div className = 'flex @4xl:flex-row flex-col @4xl:items-start gap-x-6 gap-y-4' >
91+ < div className = 'flex @4xl:flex-row flex-col @4xl:items-start gap-x-6 gap-y-4 [--badge-amber-text:#854d0e] [--badge-blue-text:#1e40af] [--badge-error-text:#991b1b] [--badge-orange-text:#9a3412] [--badge-success-text:#166534] dark:[--badge-amber-text:#fcd34d] dark:[--badge-blue-text:#93c5fd] dark:[--badge-error-text:#fca5a5] dark:[--badge-orange-text:#fdba74] dark:[--badge-success-text:#86efac] [&_button[aria-haspopup=dialog]]:min-h-6 ' >
7292 < div className = 'min-w-0 flex-1' >
7393 { slots . header }
7494 { slots . description }
7595 { slots . apiPlayground }
76- { slots . authSchemes && < div className = 'api-section-divider' > { slots . authSchemes } </ div > }
96+ { slots . authSchemes && (
97+ < div className = 'api-section-divider' > { labelAuthSelectors ( slots . authSchemes ) } </ div >
98+ ) }
7799 { slots . parameters }
78100 { slots . body && < div className = 'api-section-divider' > { slots . body } </ div > }
79101 < ResponseSection > { slots . responses } </ ResponseSection >
@@ -179,6 +201,8 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
179201 breadcrumb = { breadcrumbs }
180202 />
181203 < DocsPage
204+ role = 'main'
205+ tabIndex = { - 1 }
182206 toc = { data . toc . filter ( isContentHeading ) }
183207 breadcrumb = { {
184208 enabled : false ,
@@ -196,15 +220,12 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
196220 component : footer ,
197221 } }
198222 >
199- < div className = 'api-page-header relative mt-6 sm:mt-0' >
200- < div className = 'absolute top-1 right-0 flex items-center gap-2' >
201- < div className = 'hidden sm:flex' >
202- < LLMCopyButton content = { apiPageContent } />
203- </ div >
204- < PageNavigationArrows previous = { neighbours ?. previous } next = { neighbours ?. next } />
223+ < PageHeader title = { data . title } className = 'api-page-header' >
224+ < div className = 'hidden sm:flex' >
225+ < LLMCopyButton content = { apiPageContent } />
205226 </ div >
206- < DocsTitle className = 'mb-2' > { data . title } </ DocsTitle >
207- </ div >
227+ < PageNavigationArrows previous = { neighbours ?. previous } next = { neighbours ?. next } / >
228+ </ PageHeader >
208229 < DocsBody >
209230 < APIPage { ...apiProps } />
210231 </ DocsBody >
@@ -225,6 +246,8 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
225246 breadcrumb = { breadcrumbs }
226247 />
227248 < DocsPage
249+ role = 'main'
250+ tabIndex = { - 1 }
228251 toc = { data . toc . filter ( isContentHeading ) }
229252 full = { data . full || isAcademy }
230253 breadcrumb = { {
@@ -244,15 +267,12 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
244267 component : footer ,
245268 } }
246269 >
247- < div className = 'relative mt-6 sm:mt-0' >
248- < div className = 'absolute top-1 right-0 flex items-center gap-2' >
249- < div className = 'hidden sm:flex' >
250- < LLMCopyButton content = { markdownContent } />
251- </ div >
252- < PageNavigationArrows previous = { neighbours ?. previous } next = { neighbours ?. next } />
270+ < PageHeader title = { data . title } >
271+ < div className = 'hidden sm:flex' >
272+ < LLMCopyButton content = { markdownContent } />
253273 </ div >
254- < DocsTitle className = 'mb-2' > { data . title } </ DocsTitle >
255- </ div >
274+ < PageNavigationArrows previous = { neighbours ?. previous } next = { neighbours ?. next } / >
275+ </ PageHeader >
256276 < DocsBody >
257277 < MDX
258278 components = { {
0 commit comments