Skip to content

Commit

Permalink
Allow passing in your own id prop (#2060)
Browse files Browse the repository at this point in the history
* accept `id` as a prop where it is currently hardcoded (React)

Continuation of #2020

Co-authored-by: Olivier Louvignes <olivier@mgcrea.io>

* accept `id` as a prop where it is currently hardcoded (Vue)

* update changelog

* apply React's hook rules

Co-authored-by: Olivier Louvignes <olivier@mgcrea.io>
  • Loading branch information
RobinMalfait and mgcrea committed Dec 2, 2022
1 parent 7509124 commit 219901c
Show file tree
Hide file tree
Showing 26 changed files with 353 additions and 272 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve syncing of the `Combobox.Input` value ([#2042](https://github.com/tailwindlabs/headlessui/pull/2042))
- Fix crash when using `multiple` mode without `value` prop (uncontrolled) for `Listbox` and `Combobox` components ([#2058](https://github.com/tailwindlabs/headlessui/pull/2058))
- Apply `enter` and `enterFrom` classes in SSR for `Transition` component ([#2059](https://github.com/tailwindlabs/headlessui/pull/2059))
- Allow passing in your own `id` prop ([#2060](https://github.com/tailwindlabs/headlessui/pull/2060))

## [1.7.4] - 2022-11-03

Expand Down
40 changes: 23 additions & 17 deletions packages/@headlessui-react/src/components/combobox/combobox.tsx
Expand Up @@ -657,7 +657,6 @@ interface InputRenderPropArg {
disabled: boolean
}
type InputPropsWeControl =
| 'id'
| 'role'
| 'aria-labelledby'
| 'aria-expanded'
Expand All @@ -678,15 +677,21 @@ let Input = forwardRefWithAs(function Input<
},
ref: Ref<HTMLInputElement>
) {
let { value, onChange, displayValue, type = 'text', ...theirProps } = props
let internalId = useId()
let {
id = `headlessui-combobox-input-${internalId}`,
onChange,
displayValue,
type = 'text',
...theirProps
} = props
let data = useData('Combobox.Input')
let actions = useActions('Combobox.Input')

let inputRef = useSyncRefs(data.inputRef, ref)

let isTyping = useRef(false)

let id = `headlessui-combobox-input-${useId()}`
let d = useDisposables()

// When a `displayValue` prop is given, we should use it to transform the current selected
Expand Down Expand Up @@ -931,7 +936,6 @@ interface ButtonRenderPropArg {
value: any
}
type ButtonPropsWeControl =
| 'id'
| 'type'
| 'tabIndex'
| 'aria-haspopup'
Expand All @@ -949,8 +953,8 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
let data = useData('Combobox.Button')
let actions = useActions('Combobox.Button')
let buttonRef = useSyncRefs(data.buttonRef, ref)

let id = `headlessui-combobox-button-${useId()}`
let internalId = useId()
let { id = `headlessui-combobox-button-${internalId}`, ...theirProps } = props
let d = useDisposables()

let handleKeyDown = useEvent((event: ReactKeyboardEvent<HTMLUListElement>) => {
Expand Down Expand Up @@ -1017,7 +1021,6 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
}),
[data]
)
let theirProps = props
let ourProps = {
ref: buttonRef,
id,
Expand Down Expand Up @@ -1048,14 +1051,15 @@ interface LabelRenderPropArg {
open: boolean
disabled: boolean
}
type LabelPropsWeControl = 'id' | 'ref' | 'onClick'
type LabelPropsWeControl = 'ref' | 'onClick'

let Label = forwardRefWithAs(function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
props: Props<TTag, LabelRenderPropArg, LabelPropsWeControl>,
ref: Ref<HTMLLabelElement>
) {
let internalId = useId()
let { id = `headlessui-combobox-label-${internalId}`, ...theirProps } = props
let data = useData('Combobox.Label')
let id = `headlessui-combobox-label-${useId()}`
let actions = useActions('Combobox.Label')
let labelRef = useSyncRefs(data.labelRef, ref)

Expand All @@ -1068,7 +1072,6 @@ let Label = forwardRefWithAs(function Label<TTag extends ElementType = typeof DE
[data]
)

let theirProps = props
let ourProps = { ref: labelRef, id, onClick: handleClick }

return render({
Expand All @@ -1090,7 +1093,6 @@ type OptionsPropsWeControl =
| 'aria-activedescendant'
| 'aria-labelledby'
| 'hold'
| 'id'
| 'onKeyDown'
| 'role'
| 'tabIndex'
Expand All @@ -1106,13 +1108,12 @@ let Options = forwardRefWithAs(function Options<
},
ref: Ref<HTMLUListElement>
) {
let { hold = false, ...theirProps } = props
let internalId = useId()
let { id = `headlessui-combobox-options-${internalId}`, hold = false, ...theirProps } = props
let data = useData('Combobox.Options')

let optionsRef = useSyncRefs(data.optionsRef, ref)

let id = `headlessui-combobox-options-${useId()}`

let usesOpenClosedState = useOpenClosed()
let visible = (() => {
if (usesOpenClosedState !== null) {
Expand Down Expand Up @@ -1179,7 +1180,7 @@ interface OptionRenderPropArg {
selected: boolean
disabled: boolean
}
type ComboboxOptionPropsWeControl = 'id' | 'role' | 'tabIndex' | 'aria-disabled' | 'aria-selected'
type ComboboxOptionPropsWeControl = 'role' | 'tabIndex' | 'aria-disabled' | 'aria-selected'

let Option = forwardRefWithAs(function Option<
TTag extends ElementType = typeof DEFAULT_OPTION_TAG,
Expand All @@ -1193,11 +1194,16 @@ let Option = forwardRefWithAs(function Option<
},
ref: Ref<HTMLLIElement>
) {
let { disabled = false, value, ...theirProps } = props
let internalId = useId()
let {
id = `headlessui-combobox-option-${internalId}`,
disabled = false,
value,
...theirProps
} = props
let data = useData('Combobox.Option')
let actions = useActions('Combobox.Option')

let id = `headlessui-combobox-option-${useId()}`
let active =
data.activeOptionIndex !== null ? data.options[data.activeOptionIndex].id === id : false

Expand Down
Expand Up @@ -91,14 +91,14 @@ let DEFAULT_DESCRIPTION_TAG = 'p' as const

export let Description = forwardRefWithAs(function Description<
TTag extends ElementType = typeof DEFAULT_DESCRIPTION_TAG
>(props: Props<TTag, {}, 'id'>, ref: Ref<HTMLParagraphElement>) {
>(props: Props<TTag>, ref: Ref<HTMLParagraphElement>) {
let internalId = useId()
let { id = `headlessui-description-${internalId}`, ...theirProps } = props
let context = useDescriptionContext()
let id = `headlessui-description-${useId()}`
let descriptionRef = useSyncRefs(ref)

useIsoMorphicEffect(() => context.register(id), [id, context.register])

let theirProps = props
let ourProps = { ref: descriptionRef, ...context.props, id }

return render({
Expand Down
40 changes: 21 additions & 19 deletions packages/@headlessui-react/src/components/dialog/dialog.tsx
Expand Up @@ -140,7 +140,7 @@ let DEFAULT_DIALOG_TAG = 'div' as const
interface DialogRenderPropArg {
open: boolean
}
type DialogPropsWeControl = 'id' | 'role' | 'aria-modal' | 'aria-describedby' | 'aria-labelledby'
type DialogPropsWeControl = 'role' | 'aria-modal' | 'aria-describedby' | 'aria-labelledby'

let DialogRenderFeatures = Features.RenderStrategy | Features.Static

Expand All @@ -156,7 +156,15 @@ let DialogRoot = forwardRefWithAs(function Dialog<
},
ref: Ref<HTMLDivElement>
) {
let { open, onClose, initialFocus, __demoMode = false, ...theirProps } = props
let internalId = useId()
let {
id = `headlessui-dialog-${internalId}`,
open,
onClose,
initialFocus,
__demoMode = false,
...theirProps
} = props
let [nestedDialogCount, setNestedDialogCount] = useState(0)

let usesOpenClosedState = useOpenClosed()
Expand Down Expand Up @@ -295,8 +303,6 @@ let DialogRoot = forwardRefWithAs(function Dialog<

let [describedby, DescriptionProvider] = useDescriptions()

let id = `headlessui-dialog-${useId()}`

let contextBag = useMemo<ContextType<typeof DialogContext>>(
() => [{ dialogState, close, setTitleId }, state],
[dialogState, state, close, setTitleId]
Expand Down Expand Up @@ -381,16 +387,16 @@ let DEFAULT_OVERLAY_TAG = 'div' as const
interface OverlayRenderPropArg {
open: boolean
}
type OverlayPropsWeControl = 'id' | 'aria-hidden' | 'onClick'
type OverlayPropsWeControl = 'aria-hidden' | 'onClick'

let Overlay = forwardRefWithAs(function Overlay<
TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG
>(props: Props<TTag, OverlayRenderPropArg, OverlayPropsWeControl>, ref: Ref<HTMLDivElement>) {
let internalId = useId()
let { id = `headlessui-dialog-overlay-${internalId}`, ...theirProps } = props
let [{ dialogState, close }] = useDialogContext('Dialog.Overlay')
let overlayRef = useSyncRefs(ref)

let id = `headlessui-dialog-overlay-${useId()}`

let handleClick = useEvent((event: ReactMouseEvent) => {
if (event.target !== event.currentTarget) return
if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()
Expand All @@ -404,7 +410,6 @@ let Overlay = forwardRefWithAs(function Overlay<
[dialogState]
)

let theirProps = props
let ourProps = {
ref: overlayRef,
id,
Expand All @@ -427,16 +432,16 @@ let DEFAULT_BACKDROP_TAG = 'div' as const
interface BackdropRenderPropArg {
open: boolean
}
type BackdropPropsWeControl = 'id' | 'aria-hidden' | 'onClick'
type BackdropPropsWeControl = 'aria-hidden' | 'onClick'

let Backdrop = forwardRefWithAs(function Backdrop<
TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG
>(props: Props<TTag, BackdropRenderPropArg, BackdropPropsWeControl>, ref: Ref<HTMLDivElement>) {
let internalId = useId()
let { id = `headlessui-dialog-backdrop-${internalId}`, ...theirProps } = props
let [{ dialogState }, state] = useDialogContext('Dialog.Backdrop')
let backdropRef = useSyncRefs(ref)

let id = `headlessui-dialog-backdrop-${useId()}`

useEffect(() => {
if (state.panelRef.current === null) {
throw new Error(
Expand All @@ -450,7 +455,6 @@ let Backdrop = forwardRefWithAs(function Backdrop<
[dialogState]
)

let theirProps = props
let ourProps = {
ref: backdropRef,
id,
Expand Down Expand Up @@ -483,11 +487,11 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
props: Props<TTag, PanelRenderPropArg>,
ref: Ref<HTMLDivElement>
) {
let internalId = useId()
let { id = `headlessui-dialog-panel-${internalId}`, ...theirProps } = props
let [{ dialogState }, state] = useDialogContext('Dialog.Panel')
let panelRef = useSyncRefs(ref, state.panelRef)

let id = `headlessui-dialog-panel-${useId()}`

let slot = useMemo<PanelRenderPropArg>(
() => ({ open: dialogState === DialogStates.Open }),
[dialogState]
Expand All @@ -499,7 +503,6 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
event.stopPropagation()
})

let theirProps = props
let ourProps = {
ref: panelRef,
id,
Expand All @@ -521,15 +524,15 @@ let DEFAULT_TITLE_TAG = 'h2' as const
interface TitleRenderPropArg {
open: boolean
}
type TitlePropsWeControl = 'id'

let Title = forwardRefWithAs(function Title<TTag extends ElementType = typeof DEFAULT_TITLE_TAG>(
props: Props<TTag, TitleRenderPropArg, TitlePropsWeControl>,
props: Props<TTag, TitleRenderPropArg>,
ref: Ref<HTMLHeadingElement>
) {
let internalId = useId()
let { id = `headlessui-dialog-title-${internalId}`, ...theirProps } = props
let [{ dialogState, setTitleId }] = useDialogContext('Dialog.Title')

let id = `headlessui-dialog-title-${useId()}`
let titleRef = useSyncRefs(ref)

useEffect(() => {
Expand All @@ -542,7 +545,6 @@ let Title = forwardRefWithAs(function Title<TTag extends ElementType = typeof DE
[dialogState]
)

let theirProps = props
let ourProps = { ref: titleRef, id }

return render({
Expand Down

0 comments on commit 219901c

Please sign in to comment.