Skip to content
Draft
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
1 change: 1 addition & 0 deletions packages/react/src/Radio/Radio.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('Radio', () => {
const radio = getByRole('radio')

expect(radio).toBeDefined()
expect(radio).toHaveAttribute('type', 'radio')
})

it('renders data-component attribute', () => {
Expand Down
32 changes: 20 additions & 12 deletions packages/react/src/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
/**
Expand Down Expand Up @@ -32,7 +33,7 @@ export type RadioProps = {
* Indicates whether the radio button must be checked before the form can be submitted
*/
required?: boolean
} & InputHTMLAttributes<HTMLInputElement>
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>

/**
* An accessible, native radio component for selecting one option from a list.
Expand All @@ -56,7 +57,6 @@ const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
const radioGroupContext = useContext(RadioGroupContext)
const handleOnChange: ChangeEventHandler<HTMLInputElement> = e => {
radioGroupContext?.onChange && radioGroupContext.onChange(e)
onChange && onChange(e)
}
const name = nameProp || radioGroupContext?.name

Expand All @@ -69,17 +69,25 @@ const Radio = React.forwardRef<HTMLInputElement, RadioProps>(

return (
<input
type="radio"
value={value}
name={name}
{...mergeProps(
{
onChange: handleOnChange,
className: clsx(sharedClasses.Input, classes.Radio),
'aria-checked': checked ? ('true' as const) : ('false' as const),
},
{
...rest,
...(onChange ? {onChange} : {}),
value,
name,
disabled,
checked,
required,
className,
},
)}
ref={ref}
disabled={disabled}
checked={checked}
aria-checked={checked ? 'true' : 'false'}
required={required}
onChange={handleOnChange}
className={clsx(className, sharedClasses.Input, classes.Radio)}
{...rest}
type="radio"
data-component="Radio"
/>
)
Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/Radio/Radio.types.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Radio from '.'

export function doesNotAcceptType() {
// @ts-expect-error Radio always renders an input with type="radio"
return <Radio name="example" value="example" type="checkbox" />
}
54 changes: 31 additions & 23 deletions packages/react/src/Token/TokenBase.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {KeyboardEvent} from 'react'
import React from 'react'
import {clsx} from 'clsx'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import classes from './TokenBase.module.css'
import {defaultTokenSize, type TokenSizeKeys} from './constants'
import {isTokenInteractive} from './utils'
import {mergeProps} from '../utils/mergeProps'

export type {TokenSizeKeys}

Expand Down Expand Up @@ -55,30 +55,38 @@ const TokenBase = React.forwardRef<HTMLButtonElement | HTMLAnchorElement | HTMLS
},
forwardedRef,
) => {
const handleKeyDown = (event: KeyboardEvent<HTMLSpanElement & HTMLAnchorElement & HTMLButtonElement>) => {
if ((event.key === 'Backspace' || event.key === 'Delete') && onRemove) {
onRemove()
}
}

return (
<Component
onKeyDown={(event: KeyboardEvent<HTMLSpanElement & HTMLAnchorElement & HTMLButtonElement>) => {
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<HTMLButtonElement>)
: Component === 'a'
? (rest as React.AnchorHTMLAttributes<HTMLAnchorElement>)
: (rest as React.HTMLAttributes<HTMLSpanElement>))}
{...mergeProps(
{
onKeyDown: handleKeyDown,
className: classes.TokenBase,
'data-cursor-is-interactive': isTokenInteractive({
as: Component,
onClick: rest.onClick,
onFocus: rest.onFocus,
tabIndex: rest.tabIndex,
disabled: rest.disabled,
}),
'data-size': size,
},
{
...(Component === 'button'
? (rest as React.ButtonHTMLAttributes<HTMLButtonElement>)
: Component === 'a'
? (rest as React.AnchorHTMLAttributes<HTMLAnchorElement>)
: (rest as React.HTMLAttributes<HTMLSpanElement>)),
...(onKeyDown ? {onKeyDown} : {}),
className,
id: id?.toString(),
},
)}
// 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
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/Token/__tests__/Token.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ const testTokenComponent = (Component: React.ComponentType<React.PropsWithChildr
expect(onRemoveMock).toHaveBeenCalled()
})

it('calls onRemove before the consumer onKeyDown handler', () => {
const calls: string[] = []
const {getByText} = HTMLRender(
<Component text="token" onRemove={() => 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(<Component text="token" className="testing-class" />)
const domNode = getByText('token')
Expand Down
10 changes: 3 additions & 7 deletions packages/react/src/VisuallyHidden/VisuallyHidden.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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'

/**
Expand All @@ -13,12 +13,8 @@ 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 (
<span className={clsx(className, classes.VisuallyHidden)} {...rest}>
{children}
</span>
)
export const VisuallyHidden = ({children, ...rest}: VisuallyHiddenProps) => {
return <span {...mergeProps({className: classes.VisuallyHidden}, rest)}>{children}</span>
}

export type VisuallyHiddenProps = React.PropsWithChildren<
Expand Down
Loading