Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose missing data-disabled and data-focus attributes on the TabsPanel, MenuButton, PopoverButton and DisclosureButton components #3061

Merged
merged 8 commits into from
Mar 27, 2024
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Prefer incoming `data-*` attributes, over the ones set by Headless UI ([#3035](https://github.com/tailwindlabs/headlessui/pull/3035))
- Respect `selectedIndex` for controlled `<Tab/>` components ([#3037](https://github.com/tailwindlabs/headlessui/pull/3037))
- Prevent unnecessary execution of the `displayValue` callback in the `ComboboxInput` component ([#3048](https://github.com/tailwindlabs/headlessui/pull/3048))
- Expose missing `data-disabled` and `data-focus` attributes on the `TabsPanel`, `MenuButton`, `PopoverButton` and `DisclosureButton` components ([#3061](https://github.com/tailwindlabs/headlessui/pull/3061))

### Changed

Expand Down
21 changes: 7 additions & 14 deletions packages/@headlessui-react/src/components/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,27 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
ref: Ref<HTMLElement>
) {
let providedDisabled = useDisabled()
let { disabled = providedDisabled || false, ...theirProps } = props
let { disabled = providedDisabled || false, autoFocus = false, ...theirProps } = props

let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
let { pressed: active, pressProps } = useActivePress({ disabled })

let ourProps = mergeProps(
{
ref,
disabled: disabled || undefined,
type: theirProps.type ?? 'button',
disabled: disabled || undefined,
autoFocus,
},
focusProps,
hoverProps,
pressProps
)

let slot = useMemo(
() =>
({
disabled,
hover,
focus,
active,
autofocus: props.autoFocus ?? false,
}) satisfies ButtonRenderPropArg,
[disabled, hover, focus, active, props.autoFocus]
)
let slot = useMemo(() => {
return { disabled, hover, focus, active, autofocus: autoFocus } satisfies ButtonRenderPropArg
thecrypticace marked this conversation as resolved.
Show resolved Hide resolved
}, [disabled, hover, focus, active, autoFocus])

return render({
ourProps,
Expand Down
33 changes: 16 additions & 17 deletions packages/@headlessui-react/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function CheckboxFn<TTag extends ElementType = typeof DEFAULT_CHECKBOX_TAG, TTyp
let {
id = providedId || `headlessui-checkbox-${internalId}`,
disabled = providedDisabled || false,
autoFocus = false,
checked: controlledChecked,
defaultChecked = false,
onChange: controlledOnChange,
Expand Down Expand Up @@ -127,9 +128,9 @@ function CheckboxFn<TTag extends ElementType = typeof DEFAULT_CHECKBOX_TAG, TTyp
// This is needed so that we can "cancel" the click event when we use the `Enter` key on a button.
let handleKeyPress = useEvent((event: ReactKeyboardEvent<HTMLElement>) => event.preventDefault())

let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled ?? false })
let { pressed: active, pressProps } = useActivePress({ disabled: disabled ?? false })
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
let { pressed: active, pressProps } = useActivePress({ disabled })

let ourProps = mergeProps(
{
Expand All @@ -151,20 +152,18 @@ function CheckboxFn<TTag extends ElementType = typeof DEFAULT_CHECKBOX_TAG, TTyp
pressProps
)

let slot = useMemo(
() =>
({
checked,
disabled,
hover,
focus,
active,
indeterminate,
changing,
autofocus: props.autoFocus ?? false,
}) satisfies CheckboxRenderPropArg,
[checked, indeterminate, disabled, hover, focus, active, changing, props.autoFocus]
)
let slot = useMemo(() => {
return {
checked,
disabled,
hover,
focus,
active,
indeterminate,
changing,
autofocus: autoFocus,
} satisfies CheckboxRenderPropArg
}, [checked, indeterminate, disabled, hover, focus, active, changing, autoFocus])

let reset = useCallback(() => {
return onChange?.(defaultChecked)
Expand Down
118 changes: 61 additions & 57 deletions packages/@headlessui-react/src/components/combobox/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -770,22 +770,20 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
data.comboboxState === ComboboxState.Open
)

let slot = useMemo(
() =>
({
open: data.comboboxState === ComboboxState.Open,
disabled,
activeIndex: data.activeOptionIndex,
activeOption:
data.activeOptionIndex === null
? null
: data.virtual
? data.virtual.options[data.activeOptionIndex ?? 0]
: (data.options[data.activeOptionIndex]?.dataRef.current.value as TValue) ?? null,
value,
}) satisfies ComboboxRenderPropArg<unknown>,
[data, disabled, value]
)
let slot = useMemo(() => {
return {
open: data.comboboxState === ComboboxState.Open,
disabled,
activeIndex: data.activeOptionIndex,
activeOption:
data.activeOptionIndex === null
? null
: data.virtual
? data.virtual.options[data.activeOptionIndex ?? 0]
: (data.options[data.activeOptionIndex]?.dataRef.current.value as TValue) ?? null,
value,
} satisfies ComboboxRenderPropArg<unknown>
}, [data, disabled, value])

let selectActiveOption = useEvent(() => {
if (data.activeOptionIndex === null) return
Expand Down Expand Up @@ -958,6 +956,7 @@ export type ComboboxInputProps<
InputPropsWeControl,
{
defaultValue?: TType
disabled?: boolean
displayValue?(item: TType): string
onChange?(event: React.ChangeEvent<HTMLInputElement>): void
autoFocus?: boolean
Expand All @@ -970,18 +969,21 @@ function InputFn<
// But today is not that day..
TType = Parameters<typeof ComboboxRoot>[0]['value'],
>(props: ComboboxInputProps<TTag, TType>, ref: Ref<HTMLInputElement>) {
let data = useData('Combobox.Input')
let actions = useActions('Combobox.Input')

let internalId = useId()
let providedId = useProvidedId()
let {
id = providedId || `headlessui-combobox-input-${internalId}`,
onChange,
displayValue,
disabled = data.disabled || false,
autoFocus = false,
// @ts-ignore: We know this MAY NOT exist for a given tag but we only care when it _does_ exist.
type = 'text',
...theirProps
} = props
let data = useData('Combobox.Input')
let actions = useActions('Combobox.Input')

let inputRef = useSyncRefs(data.inputRef, ref, useFloatingReference())
let ownerDocument = useOwnerDocument(data.inputRef)
Expand Down Expand Up @@ -1320,20 +1322,18 @@ function InputFn<
let labelledBy = useLabelledBy()
let describedBy = useDescribedBy()

let { isFocused: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: data.disabled ?? false })
let { isFocused: focus, focusProps } = useFocusRing({ autoFocus })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })

let slot = useMemo(
() =>
({
open: data.comboboxState === ComboboxState.Open,
disabled: data.disabled,
hover,
focus,
autofocus: props.autoFocus ?? false,
}) satisfies InputRenderPropArg,
[data, hover, focus, props.autoFocus]
)
let slot = useMemo(() => {
return {
open: data.comboboxState === ComboboxState.Open,
disabled,
hover,
focus,
autofocus: autoFocus,
} satisfies InputRenderPropArg
}, [data, hover, focus, autoFocus, disabled])

let ourProps = mergeProps(
{
Expand Down Expand Up @@ -1365,7 +1365,8 @@ function InputFn<
? displayValue?.(data.defaultValue as unknown as TType)
: null) ??
data.defaultValue,
disabled: data.disabled,
disabled: disabled || undefined,
autoFocus,
onCompositionStart: handleCompositionStart,
onCompositionEnd: handleCompositionEnd,
onKeyDown: handleKeyDown,
Expand Down Expand Up @@ -1411,6 +1412,7 @@ export type ComboboxButtonProps<TTag extends ElementType = typeof DEFAULT_BUTTON
ButtonPropsWeControl,
{
autoFocus?: boolean
disabled?: boolean
}
>

Expand All @@ -1422,7 +1424,12 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
let actions = useActions('Combobox.Button')
let buttonRef = useSyncRefs(data.buttonRef, ref)
let internalId = useId()
let { id = `headlessui-combobox-button-${internalId}`, ...theirProps } = props
let {
id = `headlessui-combobox-button-${internalId}`,
disabled = data.disabled || false,
autoFocus = false,
...theirProps
} = props
let d = useDisposables()

let handleKeyDown = useEvent((event: ReactKeyboardEvent<HTMLUListElement>) => {
Expand Down Expand Up @@ -1479,22 +1486,20 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(

let labelledBy = useLabelledBy([id])

let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: data.disabled ?? false })
let { pressed: active, pressProps } = useActivePress({ disabled: data.disabled ?? false })
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus })
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
let { pressed: active, pressProps } = useActivePress({ disabled })

let slot = useMemo(
() =>
({
open: data.comboboxState === ComboboxState.Open,
active: active || data.comboboxState === ComboboxState.Open,
disabled: data.disabled,
value: data.value,
hover,
focus,
}) satisfies ButtonRenderPropArg,
[data, hover, focus, active]
)
let slot = useMemo(() => {
return {
open: data.comboboxState === ComboboxState.Open,
active: active || data.comboboxState === ComboboxState.Open,
disabled,
value: data.value,
hover,
focus,
} satisfies ButtonRenderPropArg
}, [data, hover, focus, active, disabled])
let ourProps = mergeProps(
{
ref: buttonRef,
Expand All @@ -1505,7 +1510,8 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
'aria-controls': data.optionsRef.current?.id,
'aria-expanded': data.comboboxState === ComboboxState.Open,
'aria-labelledby': labelledBy,
disabled: data.disabled,
disabled: disabled || undefined,
autoFocus,
onClick: handleClick,
onKeyDown: handleKeyDown,
},
Expand Down Expand Up @@ -1592,14 +1598,12 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(

let labelledBy = useLabelledBy([data.buttonRef.current?.id])

let slot = useMemo(
() =>
({
open: data.comboboxState === ComboboxState.Open,
option: undefined,
}) satisfies OptionsRenderPropArg,
[data]
)
let slot = useMemo(() => {
return {
open: data.comboboxState === ComboboxState.Open,
option: undefined,
} satisfies OptionsRenderPropArg
}, [data])
let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
'aria-labelledby': labelledBy,
role: 'listbox',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ describe('Rendering', () => {
open: false,
hover: false,
active: false,
disabled: false,
focus: false,
autofocus: false,
}),
Expand All @@ -283,6 +284,7 @@ describe('Rendering', () => {
open: true,
hover: false,
active: false,
disabled: false,
focus: false,
autofocus: false,
}),
Expand Down Expand Up @@ -310,6 +312,7 @@ describe('Rendering', () => {
open: false,
hover: false,
active: false,
disabled: false,
focus: false,
autofocus: false,
}),
Expand All @@ -325,6 +328,7 @@ describe('Rendering', () => {
open: true,
hover: false,
active: false,
disabled: false,
focus: false,
autofocus: false,
}),
Expand Down
Loading
Loading