Skip to content

Commit

Permalink
Integrate with Field
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks committed Aug 20, 2024
1 parent 94fcde0 commit e866d9d
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 56 deletions.
20 changes: 20 additions & 0 deletions packages/mui-base/src/Field/Description/FieldDescription.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as Checkbox from '@base_ui/react/Checkbox';
import * as Switch from '@base_ui/react/Switch';
import * as NumberField from '@base_ui/react/NumberField';
import * as Slider from '@base_ui/react/Slider';
import * as RadioGroup from '@base_ui/react/RadioGroup';
import * as Radio from '@base_ui/react/Radio';
import { createRenderer, screen } from '@mui/internal-test-utils';
import { expect } from 'chai';
import { describeConformance } from '../../../test/describeConformance';
Expand Down Expand Up @@ -104,5 +106,23 @@ describe('<Field.Description />', () => {
);
});
});

describe('RadioGroup', () => {
it('supports RadioGroup', () => {
render(
<Field.Root>
<RadioGroup.Root>
<Radio.Root value="1" />
</RadioGroup.Root>
<Field.Description data-testid="description" />
</Field.Root>,
);

expect(screen.getByTestId('description')).to.have.attribute(
'id',
screen.getByRole('radiogroup').getAttribute('aria-describedby')!,
);
});
});
});
});
20 changes: 20 additions & 0 deletions packages/mui-base/src/Field/Label/FieldLabel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as Checkbox from '@base_ui/react/Checkbox';
import * as Switch from '@base_ui/react/Switch';
import * as NumberField from '@base_ui/react/NumberField';
import * as Slider from '@base_ui/react/Slider';
import * as RadioGroup from '@base_ui/react/RadioGroup';
import * as Radio from '@base_ui/react/Radio';
import { createRenderer, screen } from '@mui/internal-test-utils';
import { expect } from 'chai';
import { describeConformance } from '../../../test/describeConformance';
Expand Down Expand Up @@ -95,5 +97,23 @@ describe('<Field.Label />', () => {
);
});
});

describe('RadioGroup', () => {
it('supports RadioGroup', () => {
render(
<Field.Root>
<RadioGroup.Root data-testid="radio-group">
<Radio.Root value="1" />
</RadioGroup.Root>
<Field.Label data-testid="label" />
</Field.Root>,
);

expect(screen.getByTestId('radio-group')).to.have.attribute(
'aria-labelledby',
screen.getByTestId('label').id,
);
});
});
});
});
89 changes: 89 additions & 0 deletions packages/mui-base/src/Field/Root/FieldRoot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import * as Checkbox from '@base_ui/react/Checkbox';
import * as Switch from '@base_ui/react/Switch';
import * as NumberField from '@base_ui/react/NumberField';
import * as Slider from '@base_ui/react/Slider';
import * as RadioGroup from '@base_ui/react/RadioGroup';
import * as Radio from '@base_ui/react/Radio';
import userEvent from '@testing-library/user-event';
import {
act,
createRenderer,
Expand Down Expand Up @@ -221,6 +224,7 @@ describe('<Field.Root />', () => {
</Field.Root>,
);

// eslint-disable-next-line testing-library/no-node-access
const input = container.querySelector<HTMLInputElement>('input')!;
const thumb = screen.getByTestId('thumb');

Expand All @@ -231,6 +235,27 @@ describe('<Field.Root />', () => {

expect(input).to.have.attribute('aria-invalid', 'true');
});

it('supports RadioGroup', () => {
render(
<Field.Root validate={() => 'error'}>
<RadioGroup.Root data-testid="group">
<Radio.Root value="1">One</Radio.Root>
<Radio.Root value="2">Two</Radio.Root>
</RadioGroup.Root>
<Field.Error data-testid="error" />
</Field.Root>,
);

const group = screen.getByTestId('group');

expect(group).not.to.have.attribute('aria-invalid');

fireEvent.focus(group);
fireEvent.blur(group);

expect(group).to.have.attribute('aria-invalid', 'true');
});
});
});

Expand Down Expand Up @@ -405,6 +430,50 @@ describe('<Field.Root />', () => {

expect(root).to.have.attribute('data-touched', 'true');
});

it('supports RadioGroup (click)', () => {
render(
<Field.Root>
<RadioGroup.Root data-testid="group">
<Radio.Root value="1" data-testid="control">
One
</Radio.Root>
<Radio.Root value="2">Two</Radio.Root>
</RadioGroup.Root>
</Field.Root>,
);

const group = screen.getByTestId('group');
const control = screen.getByTestId('control');

fireEvent.click(control);

expect(group).to.have.attribute('data-touched', 'true');
expect(control).to.have.attribute('data-touched', 'true');
});

it('supports RadioGroup (blur)', async () => {
render(
<Field.Root>
<RadioGroup.Root data-testid="group">
<Radio.Root value="1" data-testid="control">
One
</Radio.Root>
<Radio.Root value="2">Two</Radio.Root>
</RadioGroup.Root>
<button />
</Field.Root>,
);

const group = screen.getByTestId('group');
const control = screen.getByTestId('control');

await userEvent.tab(); // onto control
await userEvent.tab(); // onto last button

expect(group).to.have.attribute('data-touched', 'true');
expect(control).to.have.attribute('data-touched', 'true');
});
});

describe('dirty', () => {
Expand Down Expand Up @@ -505,6 +574,7 @@ describe('<Field.Root />', () => {
);

const root = screen.getByTestId('root');
// eslint-disable-next-line testing-library/no-node-access
const input = container.querySelector<HTMLInputElement>('input')!;

expect(root).not.to.have.attribute('data-dirty');
Expand All @@ -513,6 +583,25 @@ describe('<Field.Root />', () => {

expect(root).to.have.attribute('data-dirty', 'true');
});

it('supports RadioGroup', () => {
render(
<Field.Root>
<RadioGroup.Root data-testid="group">
<Radio.Root value="1">One</Radio.Root>
<Radio.Root value="2">Two</Radio.Root>
</RadioGroup.Root>
</Field.Root>,
);

const group = screen.getByTestId('group');

expect(group).not.to.have.attribute('data-dirty');

fireEvent.click(screen.getByText('One'));

expect(group).to.have.attribute('data-dirty', 'true');
});
});
});
});
14 changes: 9 additions & 5 deletions packages/mui-base/src/Radio/Root/RadioRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useRadioRoot } from './useRadioRoot';
import { RadioRootContext } from './RadioRootContext';
import { CompositeItem } from '../../Composite/Item/CompositeItem';
import { NOOP } from '../../utils/noop';
import { useFieldRootContext } from '../../Field/Root/FieldRootContext';

const customStyleHookMapping: CustomStyleHookMapping<RadioRoot.OwnerState> = {
checked(value) {
Expand All @@ -35,10 +36,12 @@ const RadioRoot = React.forwardRef(function RadioRoot(
disabled: disabledRoot,
readOnly: readOnlyRoot,
required: requiredRoot,
setCheckedItem,
setCheckedValue,
} = useRadioGroupRootContext();

const disabled = disabledRoot || disabledProp;
const { ownerState: fieldOwnerState, disabled: fieldDisabled } = useFieldRootContext();

const disabled = fieldDisabled || disabledRoot || disabledProp;
const readOnly = readOnlyRoot || readOnlyProp;
const required = requiredRoot || requiredProp;

Expand All @@ -50,15 +53,16 @@ const RadioRoot = React.forwardRef(function RadioRoot(

const ownerState: RadioRoot.OwnerState = React.useMemo(
() => ({
...fieldOwnerState,
required,
disabled,
readOnly,
checked,
}),
[disabled, readOnly, checked, required],
[fieldOwnerState, disabled, readOnly, checked, required],
);

const contextValue: RadioRootContext.Value = React.useMemo(() => ownerState, [ownerState]);
const contextValue: RadioRootContext = React.useMemo(() => ownerState, [ownerState]);

const { renderElement } = useComponentRenderer({
propGetter: getRootProps,
Expand All @@ -72,7 +76,7 @@ const RadioRoot = React.forwardRef(function RadioRoot(

return (
<RadioRootContext.Provider value={contextValue}>
{setCheckedItem === NOOP ? renderElement() : <CompositeItem render={renderElement()} />}
{setCheckedValue === NOOP ? renderElement() : <CompositeItem render={renderElement()} />}
<input {...getInputProps()} />
</RadioRootContext.Provider>
);
Expand Down
18 changes: 8 additions & 10 deletions packages/mui-base/src/Radio/Root/RadioRootContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import * as React from 'react';

export const RadioRootContext = React.createContext<RadioRootContext.Value | null>(null);
export interface RadioRootContext {
disabled: boolean;
readOnly: boolean;
checked: boolean;
required: boolean;
}

export const RadioRootContext = React.createContext<RadioRootContext | null>(null);

export function useRadioRootContext() {
const value = React.useContext(RadioRootContext);
Expand All @@ -9,12 +16,3 @@ export function useRadioRootContext() {
}
return value;
}

export namespace RadioRootContext {
export interface Value {
disabled: boolean;
readOnly: boolean;
checked: boolean;
required: boolean;
}
}
32 changes: 24 additions & 8 deletions packages/mui-base/src/Radio/Root/useRadioRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { visuallyHidden } from '../../utils/visuallyHidden';
import { useRadioGroupRootContext } from '../../RadioGroup/Root/RadioGroupRootContext';
import { useFieldRootContext } from '../../Field/Root/FieldRootContext';

/**
*
Expand All @@ -13,10 +14,12 @@ import { useRadioGroupRootContext } from '../../RadioGroup/Root/RadioGroupRootCo
export function useRadioRoot(params: useRadioRoot.Parameters) {
const { disabled, readOnly, value, required } = params;

const { checkedItem, setCheckedItem, onValueChange, touched, setTouched } =
const { checkedValue, setCheckedValue, onValueChange, touched, setTouched } =
useRadioGroupRootContext();

const checked = checkedItem === value;
const { setDirty, validityData, setTouched: setFieldTouched } = useFieldRootContext();

const checked = checkedValue === value;

const inputRef = React.useRef<HTMLInputElement>(null);

Expand All @@ -26,7 +29,7 @@ export function useRadioRoot(params: useRadioRoot.Parameters) {
role: 'radio',
type: 'button',
'aria-checked': checked,
'aria-required': required,
'aria-required': required || undefined,
'aria-disabled': disabled || undefined,
'aria-readonly': readOnly || undefined,
onKeyDown(event) {
Expand Down Expand Up @@ -59,15 +62,15 @@ export function useRadioRoot(params: useRadioRoot.Parameters) {
const getInputProps: useRadioRoot.ReturnValue['getInputProps'] = React.useCallback(
(externalProps = {}) =>
mergeReactProps<'input'>(externalProps, {
type: 'radio' as const,
type: 'radio',
ref: inputRef,
tabIndex: -1,
style: visuallyHidden,
'aria-hidden': true,
disabled,
checked,
required,
readOnly,
style: visuallyHidden,
'aria-hidden': true,
onChange(event) {
// Workaround for https://github.com/facebook/react/issues/9023
if (event.nativeEvent.defaultPrevented) {
Expand All @@ -78,11 +81,24 @@ export function useRadioRoot(params: useRadioRoot.Parameters) {
return;
}

setCheckedItem(value);
setFieldTouched(true);
setDirty(value !== validityData.initialValue);
setCheckedValue(value);
onValueChange?.(value, event);
},
}),
[disabled, readOnly, value, checked, setCheckedItem, required, onValueChange],
[
disabled,
checked,
required,
readOnly,
value,
setFieldTouched,
setDirty,
validityData.initialValue,
setCheckedValue,
onValueChange,
],
);

return React.useMemo(
Expand Down
Loading

0 comments on commit e866d9d

Please sign in to comment.