Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/happy-guests-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nextui-org/checkbox": patch
"@nextui-org/switch": patch
"@nextui-org/radio": patch
---

Fix #4210 radio, checkbox & switch interaction
34 changes: 5 additions & 29 deletions packages/components/checkbox/src/use-checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import type {AriaCheckboxProps} from "@react-types/checkbox";
import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";

import {useProviderContext} from "@nextui-org/system";
import {ReactNode, Ref, useCallback, useId, useState} from "react";
import {ReactNode, Ref, useCallback, useId} from "react";
import {useMemo, useRef} from "react";
import {useToggleState} from "@react-stately/toggle";
import {checkbox} from "@nextui-org/theme";
import {useCallbackRef} from "@nextui-org/use-callback-ref";
import {useHover, usePress} from "@react-aria/interactions";
import {useHover} from "@react-aria/interactions";
import {useFocusRing} from "@react-aria/focus";
import {mergeProps, chain} from "@react-aria/utils";
import {__DEV__, warn, clsx, dataAttr, safeAriaLabel} from "@nextui-org/shared-utils";
Expand Down Expand Up @@ -182,38 +182,15 @@ export function useCheckbox(props: UseCheckboxProps = {}) {

const toggleState = useToggleState(ariaCheckboxProps);

const {
inputProps,
isSelected,
isDisabled,
isReadOnly,
isPressed: isPressedKeyboard,
} = isInGroup
const {inputProps, isSelected, isDisabled, isReadOnly, isPressed} = isInGroup
? // eslint-disable-next-line
useReactAriaCheckboxGroupItem({...ariaCheckboxProps}, groupContext.groupState, inputRef)
: // eslint-disable-next-line
useReactAriaCheckbox({...ariaCheckboxProps}, toggleState, inputRef);

const isInteractionDisabled = isDisabled || isReadOnly;

// Handle press state for full label. Keyboard press state is returned by useCheckbox
// since it is handled on the <input> element itself.
const [isPressed, setPressed] = useState(false);
const {pressProps} = usePress({
isDisabled: isInteractionDisabled,
onPressStart(e) {
if (e.pointerType !== "keyboard") {
setPressed(true);
}
},
onPressEnd(e) {
if (e.pointerType !== "keyboard") {
setPressed(false);
}
},
});

const pressed = isInteractionDisabled ? false : isPressed || isPressedKeyboard;
const pressed = isInteractionDisabled ? false : isPressed;

const {hoverProps, isHovered} = useHover({
isDisabled: inputProps.disabled,
Expand Down Expand Up @@ -277,7 +254,7 @@ export function useCheckbox(props: UseCheckboxProps = {}) {
"data-readonly": dataAttr(inputProps.readOnly),
"data-focus-visible": dataAttr(isFocusVisible),
"data-indeterminate": dataAttr(isIndeterminate),
...mergeProps(hoverProps, pressProps, otherProps),
...mergeProps(hoverProps, otherProps),
};
}, [
slots,
Expand All @@ -292,7 +269,6 @@ export function useCheckbox(props: UseCheckboxProps = {}) {
inputProps.readOnly,
isFocusVisible,
hoverProps,
pressProps,
otherProps,
]);

Expand Down
33 changes: 5 additions & 28 deletions packages/components/radio/src/use-radio.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {AriaRadioProps} from "@react-types/radio";
import type {RadioVariantProps, RadioSlots, SlotsToClasses} from "@nextui-org/theme";

import {Ref, ReactNode, useCallback, useId, useState} from "react";
import {Ref, ReactNode, useCallback, useId} from "react";
import {useMemo, useRef} from "react";
import {useFocusRing} from "@react-aria/focus";
import {useHover, usePress} from "@react-aria/interactions";
import {useHover} from "@react-aria/interactions";
import {radio} from "@nextui-org/theme";
import {useRadio as useReactAriaRadio} from "@react-aria/radio";
import {HTMLNextUIProps, PropGetter, useProviderContext} from "@nextui-org/system";
Expand Down Expand Up @@ -115,12 +115,7 @@ export function useRadio(props: UseRadioProps) {
descriptionId,
]);

const {
inputProps,
isDisabled,
isSelected,
isPressed: isPressedKeyboard,
} = useReactAriaRadio(
const {inputProps, isDisabled, isSelected, isPressed} = useReactAriaRadio(
{
value,
children: typeof children === "function" ? true : children,
Expand All @@ -135,29 +130,11 @@ export function useRadio(props: UseRadioProps) {
});

const interactionDisabled = isDisabled || inputProps.readOnly;

// Handle press state for full label. Keyboard press state is returned by useCheckbox
// since it is handled on the <input> element itself.
const [isPressed, setPressed] = useState(false);
const {pressProps} = usePress({
isDisabled: interactionDisabled,
onPressStart(e) {
if (e.pointerType !== "keyboard") {
setPressed(true);
}
},
onPressEnd(e) {
if (e.pointerType !== "keyboard") {
setPressed(false);
}
},
});

const {hoverProps, isHovered} = useHover({
isDisabled: interactionDisabled,
});

const pressed = interactionDisabled ? false : isPressed || isPressedKeyboard;
const pressed = interactionDisabled ? false : isPressed;

const slots = useMemo(
() =>
Expand Down Expand Up @@ -189,7 +166,7 @@ export function useRadio(props: UseRadioProps) {
"data-hover-unselected": dataAttr(isHovered && !isSelected),
"data-readonly": dataAttr(inputProps.readOnly),
"aria-required": dataAttr(isRequired),
...mergeProps(hoverProps, pressProps, otherProps),
...mergeProps(hoverProps, otherProps),
};
},
[
Expand Down
32 changes: 5 additions & 27 deletions packages/components/switch/src/use-switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type {ToggleVariantProps, ToggleSlots, SlotsToClasses} from "@nextui-org/
import type {AriaSwitchProps} from "@react-aria/switch";
import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";

import {ReactNode, Ref, useCallback, useId, useRef, useState} from "react";
import {ReactNode, Ref, useCallback, useId, useRef} from "react";
import {mapPropsVariants, useProviderContext} from "@nextui-org/system";
import {mergeRefs} from "@nextui-org/react-utils";
import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect";
import {useHover, usePress} from "@react-aria/interactions";
import {useHover} from "@react-aria/interactions";
import {toggle} from "@nextui-org/theme";
import {chain, mergeProps} from "@react-aria/utils";
import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils";
Expand Down Expand Up @@ -155,36 +155,14 @@ export function useSwitch(originalProps: UseSwitchProps = {}) {
state.setSelected(isInputRefChecked);
}, [inputRef.current]);

const {
inputProps,
isPressed: isPressedKeyboard,
isReadOnly,
} = useReactAriaSwitch(ariaSwitchProps, state, inputRef);
const {inputProps, isPressed, isReadOnly} = useReactAriaSwitch(ariaSwitchProps, state, inputRef);
const {focusProps, isFocused, isFocusVisible} = useFocusRing({autoFocus: inputProps.autoFocus});
const {hoverProps, isHovered} = useHover({
isDisabled: inputProps.disabled,
});

const isInteractionDisabled = ariaSwitchProps.isDisabled || isReadOnly;

// Handle press state for full label. Keyboard press state is returned by useSwitch
// since it is handled on the <input> element itself.
const [isPressed, setPressed] = useState(false);
const {pressProps} = usePress({
isDisabled: isInteractionDisabled,
onPressStart(e) {
if (e.pointerType !== "keyboard") {
setPressed(true);
}
},
onPressEnd(e) {
if (e.pointerType !== "keyboard") {
setPressed(false);
}
},
});

const pressed = isInteractionDisabled ? false : isPressed || isPressedKeyboard;
const pressed = isInteractionDisabled ? false : isPressed;

const isSelected = inputProps.checked;
const isDisabled = inputProps.disabled;
Expand All @@ -202,7 +180,7 @@ export function useSwitch(originalProps: UseSwitchProps = {}) {

const getBaseProps: PropGetter = (props) => {
return {
...mergeProps(hoverProps, pressProps, otherProps, props),
...mergeProps(hoverProps, otherProps, props),
ref: domRef,
className: slots.base({class: clsx(baseStyles, props?.className)}),
"data-disabled": dataAttr(isDisabled),
Expand Down