fdd2629795
* 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
110 lines
2.8 KiB
TypeScript
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',
|
|
})
|
|
}
|