Skip to content

Commit 6d44a8d

Browse files
authored
Expose missing data-disabled and data-focus attributes on the TabsPanel, MenuButton, PopoverButton and DisclosureButton components (#3061)
* expose `data-focus` on the `TabsPanel` component * expose `data-disabled` on the `MenuButton` component * expose `data-disabled` on the `PopoverButton` component * expose `data-disabled` on the `DisclosureButton` component * cleanup repetition * update changelog * add `satisfies` statements to ensure all data is present * update changelog entry
1 parent bf4dc77 commit 6d44a8d

File tree

20 files changed

+349
-334
lines changed

20 files changed

+349
-334
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- Prefer incoming `data-*` attributes, over the ones set by Headless UI ([#3035](https://github.com/tailwindlabs/headlessui/pull/3035))
2121
- Respect `selectedIndex` for controlled `<Tab/>` components ([#3037](https://github.com/tailwindlabs/headlessui/pull/3037))
2222
- Prevent unnecessary execution of the `displayValue` callback in the `ComboboxInput` component ([#3048](https://github.com/tailwindlabs/headlessui/pull/3048))
23+
- Expose missing `data-disabled` and `data-focus` attributes on the `TabsPanel`, `MenuButton`, `PopoverButton` and `DisclosureButton` components ([#3061](https://github.com/tailwindlabs/headlessui/pull/3061))
2324

2425
### Changed
2526

packages/@headlessui-react/src/components/button/button.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,27 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
4141
ref: Ref<HTMLElement>
4242
) {
4343
let providedDisabled = useDisabled()
44-
let { disabled = providedDisabled || false, ...theirProps } = props
44+
let { disabled = providedDisabled || false, autoFocus = false, ...theirProps } = props
4545

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

5050
let ourProps = mergeProps(
5151
{
5252
ref,
53-
disabled: disabled || undefined,
5453
type: theirProps.type ?? 'button',
54+
disabled: disabled || undefined,
55+
autoFocus,
5556
},
5657
focusProps,
5758
hoverProps,
5859
pressProps
5960
)
6061

61-
let slot = useMemo(
62-
() =>
63-
({
64-
disabled,
65-
hover,
66-
focus,
67-
active,
68-
autofocus: props.autoFocus ?? false,
69-
}) satisfies ButtonRenderPropArg,
70-
[disabled, hover, focus, active, props.autoFocus]
71-
)
62+
let slot = useMemo(() => {
63+
return { disabled, hover, focus, active, autofocus: autoFocus } satisfies ButtonRenderPropArg
64+
}, [disabled, hover, focus, active, autoFocus])
7265

7366
return render({
7467
ourProps,

packages/@headlessui-react/src/components/checkbox/checkbox.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ function CheckboxFn<TTag extends ElementType = typeof DEFAULT_CHECKBOX_TAG, TTyp
8383
let {
8484
id = providedId || `headlessui-checkbox-${internalId}`,
8585
disabled = providedDisabled || false,
86+
autoFocus = false,
8687
checked: controlledChecked,
8788
defaultChecked = false,
8889
onChange: controlledOnChange,
@@ -127,9 +128,9 @@ function CheckboxFn<TTag extends ElementType = typeof DEFAULT_CHECKBOX_TAG, TTyp
127128
// This is needed so that we can "cancel" the click event when we use the `Enter` key on a button.
128129
let handleKeyPress = useEvent((event: ReactKeyboardEvent<HTMLElement>) => event.preventDefault())
129130

130-
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
131-
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled ?? false })
132-
let { pressed: active, pressProps } = useActivePress({ disabled: disabled ?? false })
131+
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus })
132+
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
133+
let { pressed: active, pressProps } = useActivePress({ disabled })
133134

134135
let ourProps = mergeProps(
135136
{
@@ -151,20 +152,18 @@ function CheckboxFn<TTag extends ElementType = typeof DEFAULT_CHECKBOX_TAG, TTyp
151152
pressProps
152153
)
153154

154-
let slot = useMemo(
155-
() =>
156-
({
157-
checked,
158-
disabled,
159-
hover,
160-
focus,
161-
active,
162-
indeterminate,
163-
changing,
164-
autofocus: props.autoFocus ?? false,
165-
}) satisfies CheckboxRenderPropArg,
166-
[checked, indeterminate, disabled, hover, focus, active, changing, props.autoFocus]
167-
)
155+
let slot = useMemo(() => {
156+
return {
157+
checked,
158+
disabled,
159+
hover,
160+
focus,
161+
active,
162+
indeterminate,
163+
changing,
164+
autofocus: autoFocus,
165+
} satisfies CheckboxRenderPropArg
166+
}, [checked, indeterminate, disabled, hover, focus, active, changing, autoFocus])
168167

169168
let reset = useCallback(() => {
170169
return onChange?.(defaultChecked)

packages/@headlessui-react/src/components/combobox/combobox.tsx

Lines changed: 61 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -770,22 +770,20 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
770770
data.comboboxState === ComboboxState.Open
771771
)
772772

773-
let slot = useMemo(
774-
() =>
775-
({
776-
open: data.comboboxState === ComboboxState.Open,
777-
disabled,
778-
activeIndex: data.activeOptionIndex,
779-
activeOption:
780-
data.activeOptionIndex === null
781-
? null
782-
: data.virtual
783-
? data.virtual.options[data.activeOptionIndex ?? 0]
784-
: (data.options[data.activeOptionIndex]?.dataRef.current.value as TValue) ?? null,
785-
value,
786-
}) satisfies ComboboxRenderPropArg<unknown>,
787-
[data, disabled, value]
788-
)
773+
let slot = useMemo(() => {
774+
return {
775+
open: data.comboboxState === ComboboxState.Open,
776+
disabled,
777+
activeIndex: data.activeOptionIndex,
778+
activeOption:
779+
data.activeOptionIndex === null
780+
? null
781+
: data.virtual
782+
? data.virtual.options[data.activeOptionIndex ?? 0]
783+
: (data.options[data.activeOptionIndex]?.dataRef.current.value as TValue) ?? null,
784+
value,
785+
} satisfies ComboboxRenderPropArg<unknown>
786+
}, [data, disabled, value])
789787

790788
let selectActiveOption = useEvent(() => {
791789
if (data.activeOptionIndex === null) return
@@ -958,6 +956,7 @@ export type ComboboxInputProps<
958956
InputPropsWeControl,
959957
{
960958
defaultValue?: TType
959+
disabled?: boolean
961960
displayValue?(item: TType): string
962961
onChange?(event: React.ChangeEvent<HTMLInputElement>): void
963962
autoFocus?: boolean
@@ -970,18 +969,21 @@ function InputFn<
970969
// But today is not that day..
971970
TType = Parameters<typeof ComboboxRoot>[0]['value'],
972971
>(props: ComboboxInputProps<TTag, TType>, ref: Ref<HTMLInputElement>) {
972+
let data = useData('Combobox.Input')
973+
let actions = useActions('Combobox.Input')
974+
973975
let internalId = useId()
974976
let providedId = useProvidedId()
975977
let {
976978
id = providedId || `headlessui-combobox-input-${internalId}`,
977979
onChange,
978980
displayValue,
981+
disabled = data.disabled || false,
982+
autoFocus = false,
979983
// @ts-ignore: We know this MAY NOT exist for a given tag but we only care when it _does_ exist.
980984
type = 'text',
981985
...theirProps
982986
} = props
983-
let data = useData('Combobox.Input')
984-
let actions = useActions('Combobox.Input')
985987

986988
let inputRef = useSyncRefs(data.inputRef, ref, useFloatingReference())
987989
let ownerDocument = useOwnerDocument(data.inputRef)
@@ -1320,20 +1322,18 @@ function InputFn<
13201322
let labelledBy = useLabelledBy()
13211323
let describedBy = useDescribedBy()
13221324

1323-
let { isFocused: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
1324-
let { isHovered: hover, hoverProps } = useHover({ isDisabled: data.disabled ?? false })
1325+
let { isFocused: focus, focusProps } = useFocusRing({ autoFocus })
1326+
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
13251327

1326-
let slot = useMemo(
1327-
() =>
1328-
({
1329-
open: data.comboboxState === ComboboxState.Open,
1330-
disabled: data.disabled,
1331-
hover,
1332-
focus,
1333-
autofocus: props.autoFocus ?? false,
1334-
}) satisfies InputRenderPropArg,
1335-
[data, hover, focus, props.autoFocus]
1336-
)
1328+
let slot = useMemo(() => {
1329+
return {
1330+
open: data.comboboxState === ComboboxState.Open,
1331+
disabled,
1332+
hover,
1333+
focus,
1334+
autofocus: autoFocus,
1335+
} satisfies InputRenderPropArg
1336+
}, [data, hover, focus, autoFocus, disabled])
13371337

13381338
let ourProps = mergeProps(
13391339
{
@@ -1365,7 +1365,8 @@ function InputFn<
13651365
? displayValue?.(data.defaultValue as unknown as TType)
13661366
: null) ??
13671367
data.defaultValue,
1368-
disabled: data.disabled,
1368+
disabled: disabled || undefined,
1369+
autoFocus,
13691370
onCompositionStart: handleCompositionStart,
13701371
onCompositionEnd: handleCompositionEnd,
13711372
onKeyDown: handleKeyDown,
@@ -1411,6 +1412,7 @@ export type ComboboxButtonProps<TTag extends ElementType = typeof DEFAULT_BUTTON
14111412
ButtonPropsWeControl,
14121413
{
14131414
autoFocus?: boolean
1415+
disabled?: boolean
14141416
}
14151417
>
14161418

@@ -1422,7 +1424,12 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
14221424
let actions = useActions('Combobox.Button')
14231425
let buttonRef = useSyncRefs(data.buttonRef, ref)
14241426
let internalId = useId()
1425-
let { id = `headlessui-combobox-button-${internalId}`, ...theirProps } = props
1427+
let {
1428+
id = `headlessui-combobox-button-${internalId}`,
1429+
disabled = data.disabled || false,
1430+
autoFocus = false,
1431+
...theirProps
1432+
} = props
14261433
let d = useDisposables()
14271434

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

14801487
let labelledBy = useLabelledBy([id])
14811488

1482-
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus: props.autoFocus ?? false })
1483-
let { isHovered: hover, hoverProps } = useHover({ isDisabled: data.disabled ?? false })
1484-
let { pressed: active, pressProps } = useActivePress({ disabled: data.disabled ?? false })
1489+
let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus })
1490+
let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled })
1491+
let { pressed: active, pressProps } = useActivePress({ disabled })
14851492

1486-
let slot = useMemo(
1487-
() =>
1488-
({
1489-
open: data.comboboxState === ComboboxState.Open,
1490-
active: active || data.comboboxState === ComboboxState.Open,
1491-
disabled: data.disabled,
1492-
value: data.value,
1493-
hover,
1494-
focus,
1495-
}) satisfies ButtonRenderPropArg,
1496-
[data, hover, focus, active]
1497-
)
1493+
let slot = useMemo(() => {
1494+
return {
1495+
open: data.comboboxState === ComboboxState.Open,
1496+
active: active || data.comboboxState === ComboboxState.Open,
1497+
disabled,
1498+
value: data.value,
1499+
hover,
1500+
focus,
1501+
} satisfies ButtonRenderPropArg
1502+
}, [data, hover, focus, active, disabled])
14981503
let ourProps = mergeProps(
14991504
{
15001505
ref: buttonRef,
@@ -1505,7 +1510,8 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
15051510
'aria-controls': data.optionsRef.current?.id,
15061511
'aria-expanded': data.comboboxState === ComboboxState.Open,
15071512
'aria-labelledby': labelledBy,
1508-
disabled: data.disabled,
1513+
disabled: disabled || undefined,
1514+
autoFocus,
15091515
onClick: handleClick,
15101516
onKeyDown: handleKeyDown,
15111517
},
@@ -1592,14 +1598,12 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
15921598

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

1595-
let slot = useMemo(
1596-
() =>
1597-
({
1598-
open: data.comboboxState === ComboboxState.Open,
1599-
option: undefined,
1600-
}) satisfies OptionsRenderPropArg,
1601-
[data]
1602-
)
1601+
let slot = useMemo(() => {
1602+
return {
1603+
open: data.comboboxState === ComboboxState.Open,
1604+
option: undefined,
1605+
} satisfies OptionsRenderPropArg
1606+
}, [data])
16031607
let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
16041608
'aria-labelledby': labelledBy,
16051609
role: 'listbox',

packages/@headlessui-react/src/components/disclosure/disclosure.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ describe('Rendering', () => {
268268
open: false,
269269
hover: false,
270270
active: false,
271+
disabled: false,
271272
focus: false,
272273
autofocus: false,
273274
}),
@@ -283,6 +284,7 @@ describe('Rendering', () => {
283284
open: true,
284285
hover: false,
285286
active: false,
287+
disabled: false,
286288
focus: false,
287289
autofocus: false,
288290
}),
@@ -310,6 +312,7 @@ describe('Rendering', () => {
310312
open: false,
311313
hover: false,
312314
active: false,
315+
disabled: false,
313316
focus: false,
314317
autofocus: false,
315318
}),
@@ -325,6 +328,7 @@ describe('Rendering', () => {
325328
open: true,
326329
hover: false,
327330
active: false,
331+
disabled: false,
328332
focus: false,
329333
autofocus: false,
330334
}),

0 commit comments

Comments
 (0)