Files
headlessui/packages/@headlessui-react/src/components/description/description.tsx
T
Robin Malfait fdd2629795 Improve overal codebase, use modern tech like esbuild and TypeScript 4! (#1055)
* use esbuild for React instead of tsdx

* remove tsdx from Vue

* use consistent names

* add jest and prettier

* update scripts

* ignore some folders for prettier

* run lint script instead of tsdx lint

* run prettier en-masse

This has a few changes because of the new prettier version.

* bump typescript to latest version

* make typescript happy

* cleanup playground package.json

* make esbuild a dev dependency

* make scripts consistent

* fix husky hooks

* add dedicated watch script

* add `yarn playground-react` and `yarn react-playground` (alias)

This will make sure to run a watcher for the actual @headlessui/react
package, and start a development server in the playground-react package.

* ignore formatting in the .next folder

* run prettier on playground-react package

* setup playground-vue

Still not 100% working, but getting there!

* add playground aliases in @headlessui/vue and @headlessui/react

This allows you to run `yarn react playground` or `yarn vue playground`
from the root.

* add `clean` script

* move examples folder in playground-vue to root

* ensure new lines for consistency in scripts

* fix typescript issue

* fix typescript issues in playgrounds

* make sure to run prettier on everything it can

* run prettier on all files

* improve error output

If you minify the code, then it could happen that the errors are a bit
obscure. This will hardcode the component name to improve errors.

* add the `prettier-plugin-tailwindcss` plugin, party!

* update changelog
2022-01-27 17:07:38 +01:00

110 lines
2.8 KiB
TypeScript

import React, {
createContext,
useCallback,
useContext,
useMemo,
useState,
// Types
ElementType,
ReactNode,
} from 'react'
import { Props } from '../../types'
import { useId } from '../../hooks/use-id'
import { render } from '../../utils/render'
import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect'
// ---
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 <Description /> 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<string[]>([])
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 = useCallback((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 (
<DescriptionContext.Provider value={contextBag}>
{props.children}
</DescriptionContext.Provider>
)
}
}, [setDescriptionIds]),
]
}
// ---
let DEFAULT_DESCRIPTION_TAG = 'p' as const
interface DescriptionRenderPropArg {}
type DescriptionPropsWeControl = 'id'
export function Description<TTag extends ElementType = typeof DEFAULT_DESCRIPTION_TAG>(
props: Props<TTag, DescriptionRenderPropArg, DescriptionPropsWeControl>
) {
let context = useDescriptionContext()
let id = `headlessui-description-${useId()}`
useIsoMorphicEffect(() => context.register(id), [id, context.register])
let passThroughProps = props
let propsWeControl = { ...context.props, id }
return render({
props: { ...passThroughProps, ...propsWeControl },
slot: context.slot || {},
defaultTag: DEFAULT_DESCRIPTION_TAG,
name: context.name || 'Description',
})
}