diff --git a/packages/react/src/Radio/Radio.test.tsx b/packages/react/src/Radio/Radio.test.tsx index 1a7b9756dcd..d20071927db 100644 --- a/packages/react/src/Radio/Radio.test.tsx +++ b/packages/react/src/Radio/Radio.test.tsx @@ -21,6 +21,7 @@ describe('Radio', () => { const radio = getByRole('radio') expect(radio).toBeDefined() + expect(radio).toHaveAttribute('type', 'radio') }) it('renders data-component attribute', () => { diff --git a/packages/react/src/Radio/Radio.tsx b/packages/react/src/Radio/Radio.tsx index 5ea79439a9f..78bb48abc89 100644 --- a/packages/react/src/Radio/Radio.tsx +++ b/packages/react/src/Radio/Radio.tsx @@ -5,6 +5,7 @@ import {clsx} from 'clsx' import sharedClasses from '../Checkbox/shared.module.css' import classes from './Radio.module.css' import type {WithSlotMarker} from '../utils/types' +import {mergeProps} from '../utils/mergeProps' export type RadioProps = { /** @@ -32,31 +33,20 @@ export type RadioProps = { * Indicates whether the radio button must be checked before the form can be submitted */ required?: boolean -} & InputHTMLAttributes +} & Omit, 'type'> /** * An accessible, native radio component for selecting one option from a list. */ const Radio = React.forwardRef( ( - { - checked, - disabled, - name: nameProp, - onChange, - required, - value, - className, - 'aria-hidden': ariaHidden = false, - ...rest - }: RadioProps, + {checked, name: nameProp, className, 'aria-hidden': ariaHidden, ...rest}: RadioProps, ref, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): ReactElement => { const radioGroupContext = useContext(RadioGroupContext) const handleOnChange: ChangeEventHandler = e => { radioGroupContext?.onChange && radioGroupContext.onChange(e) - onChange && onChange(e) } const name = nameProp || radioGroupContext?.name @@ -69,17 +59,19 @@ const Radio = React.forwardRef( return ( ) diff --git a/packages/react/src/Radio/Radio.types.test.tsx b/packages/react/src/Radio/Radio.types.test.tsx new file mode 100644 index 00000000000..a08856d1c1e --- /dev/null +++ b/packages/react/src/Radio/Radio.types.test.tsx @@ -0,0 +1,6 @@ +import Radio from '.' + +export function doesNotAcceptType() { + // @ts-expect-error Radio always renders an input with type="radio" + return +} diff --git a/packages/react/src/Token/TokenBase.tsx b/packages/react/src/Token/TokenBase.tsx index 03202c6f0cb..dd50a47c10c 100644 --- a/packages/react/src/Token/TokenBase.tsx +++ b/packages/react/src/Token/TokenBase.tsx @@ -5,6 +5,7 @@ import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../uti import classes from './TokenBase.module.css' import {defaultTokenSize, type TokenSizeKeys} from './constants' import {isTokenInteractive} from './utils' +import {mergeProps} from '../utils/mergeProps' export type {TokenSizeKeys} @@ -43,46 +44,43 @@ export interface TokenBaseProps const TokenBase = React.forwardRef( ( - { - onRemove, - onKeyDown, - id, - className, - size = defaultTokenSize, - isSelected: _isSelected, - as: Component = 'span', - ...rest - }, + {onRemove, id, className, size = defaultTokenSize, isSelected: _isSelected, as: Component = 'span', ...rest}, forwardedRef, ) => { + const handleKeyDown = (event: KeyboardEvent) => { + if ((event.key === 'Backspace' || event.key === 'Delete') && onRemove) { + onRemove() + } + } + const consumerProps = + Component === 'button' + ? (rest as React.ButtonHTMLAttributes) + : Component === 'a' + ? (rest as React.AnchorHTMLAttributes) + : (rest as React.HTMLAttributes) + return ( ) => { - onKeyDown && onKeyDown(event) - - if ((event.key === 'Backspace' || event.key === 'Delete') && onRemove) { - onRemove() - } - }} - className={clsx(classes.TokenBase, className)} - data-cursor-is-interactive={isTokenInteractive({ - as: Component, - onClick: rest.onClick, - onFocus: rest.onFocus, - tabIndex: rest.tabIndex, - disabled: rest.disabled, - })} - data-size={size} - id={id?.toString()} - {...(Component === 'button' - ? (rest as React.ButtonHTMLAttributes) - : Component === 'a' - ? (rest as React.AnchorHTMLAttributes) - : (rest as React.HTMLAttributes))} // TypeScript cannot resolve polymorphic ref types at compile time // This assertion is safe because the ref will match the actual rendered element // eslint-disable-next-line @typescript-eslint/no-explicit-any ref={forwardedRef as any} + {...mergeProps( + { + onKeyDown: handleKeyDown, + className: clsx(classes.TokenBase, className), + 'data-cursor-is-interactive': isTokenInteractive({ + as: Component, + onClick: rest.onClick, + onFocus: rest.onFocus, + tabIndex: rest.tabIndex, + disabled: rest.disabled, + }), + 'data-size': size, + id: id?.toString(), + }, + consumerProps, + )} /> ) }, diff --git a/packages/react/src/Token/__tests__/Token.test.tsx b/packages/react/src/Token/__tests__/Token.test.tsx index 73c01124c2a..ac07bbe22fe 100644 --- a/packages/react/src/Token/__tests__/Token.test.tsx +++ b/packages/react/src/Token/__tests__/Token.test.tsx @@ -63,6 +63,17 @@ const testTokenComponent = (Component: React.ComponentType { + const calls: string[] = [] + const {getByText} = HTMLRender( + calls.push('remove')} onKeyDown={() => calls.push('consumer')} />, + ) + + fireEvent.keyDown(getByText('token'), {key: 'Backspace'}) + + expect(calls).toEqual(['remove', 'consumer']) + }) + it('adds className to rendered component', () => { const {getByText} = HTMLRender() const domNode = getByText('token') diff --git a/packages/react/src/VisuallyHidden/VisuallyHidden.tsx b/packages/react/src/VisuallyHidden/VisuallyHidden.tsx index d05c496352f..dd9aefbdb9d 100644 --- a/packages/react/src/VisuallyHidden/VisuallyHidden.tsx +++ b/packages/react/src/VisuallyHidden/VisuallyHidden.tsx @@ -1,6 +1,7 @@ import {clsx} from 'clsx' import type React from 'react' import {type HTMLAttributes} from 'react' +import {mergeProps} from '../utils/mergeProps' import classes from './VisuallyHidden.module.css' /** @@ -14,11 +15,7 @@ import classes from './VisuallyHidden.module.css' * @see https://www.scottohara.me/blog/2023/03/21/visually-hidden-hack.html */ export const VisuallyHidden = ({className, children, ...rest}: VisuallyHiddenProps) => { - return ( - - {children} - - ) + return {children} } export type VisuallyHiddenProps = React.PropsWithChildren<