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, { // })