Files
headlessui/packages/@headlessui-react/src/components/label/label.tsx
T
Robin Malfait e819c0a7b2 General/random internal cleanup (part 1) (#1484)
* sort React imports

* improve type signature of the `useEvent` hook

* use more correct `useIsoMorphicEffect` check in `useEvent`

* refactor `useCallback` to cleaner `useEvent`

* convert `const` to `let`

Just for consistency..

* cleanup `Tabs` code

Created explicit functions that can be called from child components
instead of calling `dispatch` directly. Introduced a `useData` and
`useActions` hook to make child components easier.

The seperation of `useData` allows us to pass down props directly
instead of going via the `useReducer` hook and dispatching actions to
make values up to date.

* cleanup `Combobox` code

* cleanup `RadioGroup` code
2022-05-23 11:26:22 +02:00

118 lines
2.9 KiB
TypeScript

import React, {
createContext,
useContext,
useMemo,
useState,
// Types
ElementType,
ReactNode,
Ref,
} from 'react'
import { Props } from '../../types'
import { useId } from '../../hooks/use-id'
import { forwardRefWithAs, 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 LabelContext = createContext<({ register(value: string): () => void } & SharedData) | null>(
null
)
function useLabelContext() {
let context = useContext(LabelContext)
if (context === null) {
let err = new Error('You used a <Label /> component, but it is not inside a relevant parent.')
if (Error.captureStackTrace) Error.captureStackTrace(err, useLabelContext)
throw err
}
return context
}
interface LabelProviderProps extends SharedData {
children: ReactNode
}
export function useLabels(): [string | undefined, (props: LabelProviderProps) => JSX.Element] {
let [labelIds, setLabelIds] = useState<string[]>([])
return [
// The actual id's as string or undefined.
labelIds.length > 0 ? labelIds.join(' ') : undefined,
// The provider component
useMemo(() => {
return function LabelProvider(props: LabelProviderProps) {
let register = useEvent((value: string) => {
setLabelIds((existing) => [...existing, value])
return () =>
setLabelIds((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 <LabelContext.Provider value={contextBag}>{props.children}</LabelContext.Provider>
}
}, [setLabelIds]),
]
}
// ---
let DEFAULT_LABEL_TAG = 'label' as const
export let Label = forwardRefWithAs(function Label<
TTag extends ElementType = typeof DEFAULT_LABEL_TAG
>(
props: Props<TTag, {}, 'id'> & {
passive?: boolean
},
ref: Ref<HTMLLabelElement>
) {
let { passive = false, ...theirProps } = props
let context = useLabelContext()
let id = `headlessui-label-${useId()}`
let labelRef = useSyncRefs(ref)
useIsoMorphicEffect(() => context.register(id), [id, context.register])
let ourProps = { ref: labelRef, ...context.props, id }
if (passive) {
if ('onClick' in ourProps) {
delete (ourProps as any)['onClick']
}
if ('onClick' in theirProps) {
delete (theirProps as any)['onClick']
}
}
return render({
ourProps,
theirProps,
slot: context.slot || {},
defaultTag: DEFAULT_LABEL_TAG,
name: context.name || 'Label',
})
})