Skip to content

Commit 631dfe7

Browse files
api updates and tests for radio primitives (#1200)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 0dcd468 commit 631dfe7

File tree

16 files changed

+339
-170
lines changed

16 files changed

+339
-170
lines changed

src/components/ui/TabNav/fragments/TabNavLink.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ const TabNavLink = ({ value, className = '', href = '#', children, disabled, asC
3939
asChild={asChild}
4040
aria-disabled={disabled}
4141
aria-selected={isActive}
42-
// @ts-expect-error
4342
disabled={disabled}
4443
{...disabled ? {} : { href }}
4544
>

src/core/primitives/Primitive/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ const SUPPORTED_HTML_ELEMENTS = ['div', 'span', 'button', 'input', 'a', 'img', '
55
type SupportedElement = typeof SUPPORTED_HTML_ELEMENTS[number];
66

77
// Update type definitions to be more specific
8-
interface PrimitiveProps extends React.HTMLAttributes<HTMLElement> {
9-
asChild?: boolean;
10-
children?: React.ReactNode;
11-
}
8+
type PrimitiveProps =
9+
| (React.InputHTMLAttributes<HTMLInputElement> & { asChild?: boolean })
10+
| (React.HTMLAttributes<HTMLElement> & { asChild?: boolean, children?: React.ReactNode });
1211

1312
// Update component creation with proper typing
1413
const createPrimitiveComponent = (elementType: SupportedElement) => {

src/core/primitives/Radio/RadioPrimitive.stories.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ export default {
88
render: (args:React.ComponentProps<typeof RadioPrimitive>) => <SandboxEditor>
99

1010
<div className='flex flex-col gap-2'>
11+
1112
<span>
12-
<RadioPrimitive name='radio' value='radio' checked={true} />
13-
<label htmlFor='radio'>Radio 1</label>
14-
</span>
15-
<span>
16-
<RadioPrimitive name='radio' value='radio2'/>
17-
<label htmlFor='radio2'>Radio 2</label>
13+
<form>
14+
<RadioPrimitive {...args}/>
15+
<label htmlFor='radio'>Radio 1</label>
16+
</form>
1817
</span>
1918
</div>
2019
</SandboxEditor>
@@ -23,8 +22,23 @@ export default {
2322

2423
export const All = {
2524
args: {
25+
onClick: (data: any) => {
26+
console.log('data', data);
27+
},
28+
name: 'radio',
29+
value: 'radio',
30+
required: true
31+
}
32+
};
33+
34+
export const Disabled = {
35+
args: {
36+
disabled: true,
37+
name: 'radio',
38+
value: 'radio1',
2639
onClick: (data: any) => {
2740
console.log('data', data);
2841
}
42+
2943
}
3044
};
Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
import React, { useState } from 'react';
2-
import Primitive from '~/core/primitives/Primitive';
1+
import React from 'react';
2+
import Primitive from '../Primitive';
33

4-
export type RadioPrimitiveProps = {
5-
onClick?: (data: any) => void;
6-
onChange?: (data: any) => void;
7-
checked?: boolean;
4+
type RadioPrimitiveProps = {
85
name: string;
96
value: string;
10-
};
11-
12-
const RadioPrimitive = ({ name = '', value = '', checked = false, onClick, onChange, ...props }:RadioPrimitiveProps) => {
13-
const [isChecked, setIsChecked] = useState(checked);
14-
const dataAttributes = {
15-
'data-checked': isChecked.toString()
16-
};
17-
18-
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
19-
const isChecked = event.target.checked;
20-
console.log(isChecked);
21-
if (typeof onChange === 'function') {
22-
setIsChecked(isChecked);
23-
onChange({
24-
value,
25-
checked: isChecked
26-
});
27-
}
28-
};
7+
id: string;
8+
onChange?: () => void;
9+
checked?: boolean;
10+
required?: boolean;
11+
disabled?: boolean;
12+
asChild?: boolean
13+
}
2914

15+
function RadioPrimitive({ name, value, id, checked, required, onChange, disabled, asChild, ...props }: RadioPrimitiveProps) {
3016
return (
31-
<Primitive.input id={value} type='radio' value={value} name={name} onChange={handleOnChange} {...props} {...dataAttributes}/>
17+
<Primitive.input
18+
type="radio"
19+
checked={checked}
20+
name={name}
21+
tabIndex={-1}
22+
value={value}
23+
onChange={onChange}
24+
id={value}
25+
aria-disabled={disabled}
26+
disabled={disabled}
27+
required={required}
28+
aria-required={required}
29+
asChild={asChild}
30+
{...props}
31+
/>
3232
);
33-
};
33+
}
3434

3535
export default RadioPrimitive;

src/core/primitives/RadioGroup/RadioGroupPrimitive.stories.js

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React, { useState, ChangeEvent, FormEvent } from 'react';
2+
import RadioGroupPrimitive from './RadioGroupPrimitive';
3+
import SandboxEditor from '~/components/tools/SandboxEditor/SandboxEditor';
4+
5+
interface Option {
6+
id: string;
7+
value: string;
8+
label: string;
9+
}
10+
11+
interface RadioButtonProps {
12+
// Add any props you want to pass to RadioButton here
13+
}
14+
15+
const RadioButton = (args: RadioButtonProps) => {
16+
const options: Option[] = [
17+
{ id: 'html', value: 'html', label: 'HTML' },
18+
{ id: 'css', value: 'css', label: 'CSS' },
19+
{ id: 'javascript', value: 'javascript', label: 'JavaScript' }
20+
];
21+
22+
const [language, setLanguage] = useState<string>('css');
23+
24+
const handleChange = (item: string) => {
25+
setLanguage(item);
26+
};
27+
return (
28+
<SandboxEditor>
29+
<RadioGroupPrimitive.Root
30+
orientation='horizontal' dir='rtl'
31+
name='test'
32+
// defaultValue='css'
33+
>
34+
{options.map((option) => (
35+
<RadioGroupPrimitive.Item key={option.id} value={option.value}>
36+
{option.label}
37+
</RadioGroupPrimitive.Item>
38+
))}
39+
</RadioGroupPrimitive.Root>
40+
</SandboxEditor>
41+
);
42+
};
43+
44+
export default {
45+
title: 'Primitives/RadioGroupPrimitive',
46+
component: RadioGroupPrimitive,
47+
render: (args: RadioButtonProps) => <RadioButton {...args}/>
48+
};
49+
50+
export const All = {};
51+
52+
export const InForm = () => {
53+
const options: Option[] = [
54+
{ id: 'html', value: 'html', label: 'HTML' },
55+
{ id: 'css', value: 'css', label: 'CSS' },
56+
{ id: 'javascript', value: 'javascript', label: 'JavaScript' }
57+
];
58+
const [selected, setSelected] = useState<string>('css');
59+
const [submitted, setSubmitted] = useState<string | null>(null);
60+
61+
const handleChange = (value: string) => {
62+
setSelected(value);
63+
};
64+
65+
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
66+
e.preventDefault();
67+
setSubmitted(selected);
68+
};
69+
70+
return (
71+
<SandboxEditor>
72+
<form onSubmit={handleSubmit}>
73+
<RadioGroupPrimitive.Root
74+
// orientation="horizontal"
75+
name="language"
76+
value={selected}
77+
onValueChange={handleChange}
78+
>
79+
{options.map((option) => (
80+
<RadioGroupPrimitive.Item key={option.id} value={option.value}>
81+
{option.label}
82+
</RadioGroupPrimitive.Item>
83+
))}
84+
</RadioGroupPrimitive.Root>
85+
<button type="submit" style={{ marginTop: 16 }}>Submit</button>
86+
{submitted && (
87+
<div style={{ marginTop: 12 }}>
88+
<strong>Submitted value:</strong> {submitted}
89+
</div>
90+
)}
91+
</form>
92+
</SandboxEditor>
93+
);
94+
};

src/core/primitives/RadioGroup/RadioGroupPrimitive.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ export type RadioGroupProps = {
88

99
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> & PropsWithChildren
1010

11-
const RadioGroupPrimitive = {} as const;
11+
const RadioGroupPrimitive = () => {
12+
console.warn('Direct usage of RadioGroup is not supported. Please use RadioGroup.Root, RadioGroup.Item, etc. instead.');
13+
return null;
14+
};
1215

1316
RadioGroupPrimitive.Root = RadioGroupPrimitiveRoot;
1417
RadioGroupPrimitive.Item = RadioGroupPrimitiveItem;
1518

19+
1620
export default RadioGroupPrimitive;
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React from 'react';
22

3-
const RadioGroupContext = React.createContext({
4-
defaultChecked: null,
5-
onChange: null
3+
export interface RadioGroupContextValue {
4+
selectedValue: string;
5+
setSelectedValue: (value: string) => void;
6+
onChange?: (value: string) => void;
7+
groupDisabled: boolean;
8+
name: string;
9+
}
610

7-
});
11+
const RadioGroupContext = React.createContext<RadioGroupContextValue | null>(null);
812

913
export default RadioGroupContext;

src/core/primitives/RadioGroup/fragments/RadioGroupPrimitiveItem.tsx

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
import React, { PropsWithChildren, useContext } from 'react';
22
import RadioGroupContext from '../context/RadioGroupContext';
3-
3+
import RovingFocusGroup from '~/core/utils/RovingFocusGroup';
44
import RadioPrimitive from '~/core/primitives/Radio';
55

6-
type RadioGroupItemProps = PropsWithChildren<{
6+
type RadioGroupPrimitiveItemProps = PropsWithChildren<{
77
value: string;
8+
disabled?: boolean
9+
children?: React.ReactNode;
10+
required?: boolean
811
}>;
912

10-
const RadioGroupPrimitiveItem = ({ value, children, ...props }: RadioGroupPrimitiveItemProps) => {
11-
const { setCheckedItem, checkedItem, onChange } = useContext(RadioGroupContext);
13+
const RadioGroupPrimitiveItem = ({ value, children, disabled, required = false, ...props }: RadioGroupPrimitiveItemProps) => {
14+
const context = useContext(RadioGroupContext);
15+
if (!context) {
16+
throw new Error('RadioGroup.Item must be used within a RadioGroup.Root');
17+
}
18+
const { groupDisabled, name, selectedValue, setSelectedValue } = context;
19+
20+
return (
21+
<RovingFocusGroup.Item role='radio'>
22+
<div {...props} onFocus={() => setSelectedValue(value)}>
1223

13-
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
14-
setCheckedItem(value);
24+
<RadioPrimitive
25+
id={value}
26+
name={name}
27+
value={value}
28+
checked = {selectedValue === value}
29+
disabled={groupDisabled || disabled}
30+
required={required}
31+
onChange={() => setSelectedValue(value)}
32+
/>
1533

16-
if (typeof onChange === 'function') {
17-
onChange(value);
18-
}
19-
};
34+
<label htmlFor={value}>{children}</label>
2035

21-
return (
22-
<div {...props}>
23-
<RadioPrimitive value={value} name='radio' checked={checkedItem === value} onChange={handleOnChange} />
24-
<label htmlFor={value}>{children}</label>
25-
</div>
36+
</div>
37+
</RovingFocusGroup.Item>
2638
);
2739
};
2840

0 commit comments

Comments
 (0)