Skip to content

Commit

Permalink
feat: forward ref for <Radio> (#2149)
Browse files Browse the repository at this point in the history
* feat: forward ref for <Radio>

* Create mean-moose-grab.md

* Update mean-moose-grab.md
  • Loading branch information
sebald authored Jun 8, 2022
1 parent 33c54b3 commit 1a0070a
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 65 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-moose-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@marigold/components": minor
---

feat: forward ref for `<Radio>`
18 changes: 18 additions & 0 deletions packages/components/src/Radio/Radio.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,21 @@ test('allows styling "read-only" state via theme', () => {
const radio = getVisibleRadios()?.[0];
expect(radio).toHaveStyle(`opacity: 0.5`);
});

test('forwards ref', () => {
const ref = React.createRef<HTMLInputElement>();
render(
<ThemeProvider theme={theme}>
<Radio.Group label="With Label">
<Radio value="1" data-testid="radio-1" ref={ref}>
Option 1
</Radio>
<Radio value="2" data-testid="radio-2">
Option 2
</Radio>
</Radio.Group>
</ThemeProvider>
);

expect(ref.current).toBeInstanceOf(HTMLInputElement);
});
148 changes: 83 additions & 65 deletions packages/components/src/Radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import React, { useRef } from 'react';
import React, {
forwardRef,
type ForwardRefExoticComponent,
type RefAttributes,
} from 'react';
import { useHover } from '@react-aria/interactions';
import { useFocusRing } from '@react-aria/focus';
import { useRadio } from '@react-aria/radio';
import { useObjectRef } from '@react-aria/utils';
import type { AriaRadioProps } from '@react-types/radio';

import {
Expand All @@ -13,7 +18,7 @@ import {
useComponentStyles,
useStateProps,
} from '@marigold/system';
import { ComponentProps } from '@marigold/types';
import type { ComponentProps } from '@marigold/types';

import { useRadioGroupContext } from './Context';
import { RadioGroup } from './RadioGroup';
Expand Down Expand Up @@ -80,75 +85,88 @@ export interface RadioProps

// Component
// ---------------
export const Radio = ({ width, disabled, ...props }: RadioProps) => {
const {
variant,
size,
error,
width: groupWidth,
...state
} = useRadioGroupContext();
export const Radio = forwardRef<HTMLInputElement, RadioProps>(
({ width, disabled, ...props }, ref) => {
const {
variant,
size,
error,
width: groupWidth,
...state
} = useRadioGroupContext();

const ref = useRef(null);
const { inputProps } = useRadio(
{ isDisabled: disabled, ...props },
state,
ref
);
const inputRef = useObjectRef(ref);
const { inputProps } = useRadio(
{ isDisabled: disabled, ...props },
state,
inputRef
);

const styles = useComponentStyles(
'Radio',
{ variant: variant || props.variant, size: size || props.size },
{ parts: ['container', 'label', 'radio'] }
);
const styles = useComponentStyles(
'Radio',
{ variant: variant || props.variant, size: size || props.size },
{ parts: ['container', 'label', 'radio'] }
);

const { hoverProps, isHovered } = useHover({});
const { isFocusVisible, focusProps } = useFocusRing();
const stateProps = useStateProps({
hover: isHovered,
focus: isFocusVisible,
checked: inputProps.checked,
disabled: inputProps.disabled,
readOnly: props.readOnly,
error,
});
const { hoverProps, isHovered } = useHover({});
const { isFocusVisible, focusProps } = useFocusRing();
const stateProps = useStateProps({
hover: isHovered,
focus: isFocusVisible,
checked: inputProps.checked,
disabled: inputProps.disabled,
readOnly: props.readOnly,
error,
});

return (
<Box
as="label"
__baseCSS={{
display: 'flex',
alignItems: 'center',
gap: '1ch',
position: 'relative',
width: width || groupWidth || '100%',
}}
css={styles.container}
{...hoverProps}
{...stateProps}
>
return (
<Box
as="input"
ref={ref}
css={{
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
zIndex: 1,
opacity: 0.0001,
cursor: inputProps.disabled ? 'not-allowed' : 'pointer',
as="label"
__baseCSS={{
display: 'flex',
alignItems: 'center',
gap: '1ch',
position: 'relative',
width: width || groupWidth || '100%',
}}
{...inputProps}
{...focusProps}
/>
<Icon checked={inputProps.checked} css={styles.radio} {...stateProps} />
<Box css={styles.label} {...stateProps}>
{props.children}
css={styles.container}
{...hoverProps}
{...stateProps}
>
<Box
as="input"
ref={inputRef}
css={{
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
zIndex: 1,
opacity: 0.0001,
cursor: inputProps.disabled ? 'not-allowed' : 'pointer',
}}
{...inputProps}
{...focusProps}
/>
<Icon checked={inputProps.checked} css={styles.radio} {...stateProps} />
<Box css={styles.label} {...stateProps}>
{props.children}
</Box>
</Box>
</Box>
);
};
);
}
) as RadioComponent;

Radio.Group = RadioGroup;

/**
* We need this so that TypeScripts allows us to add
* additional properties to the component (function).
*/
export interface RadioComponent
extends ForwardRefExoticComponent<
RadioProps & RefAttributes<HTMLInputElement>
> {
Group: typeof RadioGroup;
}

0 comments on commit 1a0070a

Please sign in to comment.