import React, {
createContext,
useContext,
useMemo,
useState,
// Types
ElementType,
ReactNode,
Ref,
} from 'react'
import { Props } from '../../types'
import { useId } from '../../hooks/use-id'
import { forwardRefWithAs, HasDisplayName, RefProp, render } from '../../utils/render'
import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'
import { useSyncRefs } from '../../hooks/use-sync-refs'
import { useEvent } from '../../hooks/use-event'
// ---
interface SharedData {
slot?: {}
name?: string
props?: {}
}
let DescriptionContext = createContext<
({ register(value: string): () => void } & SharedData) | null
>(null)
function useDescriptionContext() {
let context = useContext(DescriptionContext)
if (context === null) {
let err = new Error(
'You used a component, but it is not inside a relevant parent.'
)
if (Error.captureStackTrace) Error.captureStackTrace(err, useDescriptionContext)
throw err
}
return context
}
interface DescriptionProviderProps extends SharedData {
children: ReactNode
}
export function useDescriptions(): [
string | undefined,
(props: DescriptionProviderProps) => JSX.Element
] {
let [descriptionIds, setDescriptionIds] = useState([])
return [
// The actual id's as string or undefined
descriptionIds.length > 0 ? descriptionIds.join(' ') : undefined,
// The provider component
useMemo(() => {
return function DescriptionProvider(props: DescriptionProviderProps) {
let register = useEvent((value: string) => {
setDescriptionIds((existing) => [...existing, value])
return () =>
setDescriptionIds((existing) => {
let clone = existing.slice()
let idx = clone.indexOf(value)
if (idx !== -1) clone.splice(idx, 1)
return clone
})
})
let contextBag = useMemo(
() => ({ register, slot: props.slot, name: props.name, props: props.props }),
[register, props.slot, props.name, props.props]
)
return (
{props.children}
)
}
}, [setDescriptionIds]),
]
}
// ---
let DEFAULT_DESCRIPTION_TAG = 'p' as const
export type DescriptionProps =
Props
function DescriptionFn(
props: DescriptionProps,
ref: Ref
) {
let internalId = useId()
let { id = `headlessui-description-${internalId}`, ...theirProps } = props
let context = useDescriptionContext()
let descriptionRef = useSyncRefs(ref)
useIsoMorphicEffect(() => context.register(id), [id, context.register])
let ourProps = { ref: descriptionRef, ...context.props, id }
return render({
ourProps,
theirProps,
slot: context.slot || {},
defaultTag: DEFAULT_DESCRIPTION_TAG,
name: context.name || 'Description',
})
}
// ---
export interface _internal_ComponentDescription extends HasDisplayName {
(
props: DescriptionProps & RefProp
): JSX.Element
}
let DescriptionRoot = forwardRefWithAs(DescriptionFn) as unknown as _internal_ComponentDescription
export let Description = Object.assign(DescriptionRoot, {
//
})