Skip to content

Commit c323764

Browse files
author
Hector Arce De Las Heras
committed
Update RadioButton component with new prop
This commit introduces several updates to the RadioButton component. New optional props `error` and `disabled` have been added, and the `RadioButtonOptionType` has been updated with additional fields: `errorMessage`, `errorIcon`, `errorIconAltText`, `errorAriaLiveType`, `error`, and `disabled`. The existing `state` prop will be deprecated, and the new `checked`, `disabled`, and `error` props will be used to control the state of the RadioButton internally. These changes enhance the flexibility and usability of the RadioButton component.
1 parent c79aaa0 commit c323764

16 files changed

+368
-81
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { buildAriaLabelledBy } from '../utils';
2+
3+
const descriptionId = 'descriptionId';
4+
const screenReaderId = 'screenReaderId';
5+
const errorMessage = 'error';
6+
const errorMessageId = '1234';
7+
const error = false;
8+
9+
describe('Utils aria', () => {
10+
it('If descriptionId no exits', () => {
11+
const descriptionId = undefined;
12+
expect(
13+
buildAriaLabelledBy({
14+
descriptionId,
15+
screenReaderId,
16+
errorMessage,
17+
errorMessageId,
18+
error,
19+
})
20+
).toBe('screenReaderId');
21+
});
22+
it('If descriptionId and error', () => {
23+
const error = true;
24+
expect(
25+
buildAriaLabelledBy({
26+
descriptionId,
27+
screenReaderId,
28+
errorMessage,
29+
errorMessageId,
30+
error,
31+
})
32+
).toBe('descriptionId screenReaderId 1234');
33+
});
34+
});

src/components/radioButtonGroup/__tests__/radioButtonGroup.test.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const mockProps: IRadioButtonGroupUncontrolled = {
2626
{
2727
label: 'Secret Fruit',
2828
value: '?',
29-
state: RadioButtonStateType.DISABLED,
29+
disabled: true,
3030
},
3131
{
3232
label: 'Kiwis',
@@ -42,6 +42,8 @@ const mockProps: IRadioButtonGroupUncontrolled = {
4242
label: 'Apple',
4343
value: 'A',
4444
description: 'An apple a day keeps the doctor await',
45+
errorMessage: 'Error text',
46+
error: true,
4547
screenReader: true,
4648
},
4749
],
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { RadioButtonStateType } from '../types';
2+
import { getState, radioButtonState } from '../utils';
3+
4+
const checked = undefined;
5+
const disabled = undefined;
6+
const error = undefined;
7+
const state = undefined;
8+
9+
describe('Utils state', () => {
10+
it('If disabled, it should return DISABLED state', () => {
11+
const disabled = true;
12+
expect(getState(checked, disabled, error, state)).toBe(RadioButtonStateType.DISABLED);
13+
});
14+
it('If disabled with checked, it should return DISABLED_SELECTED state', () => {
15+
const checked = true;
16+
const disabled = true;
17+
expect(getState(checked, disabled, error, state)).toBe(RadioButtonStateType.DISABLED_SELECTED);
18+
});
19+
it('If error, it should return ERROR state', () => {
20+
const error = true;
21+
expect(getState(checked, disabled, error, state)).toBe(RadioButtonStateType.ERROR);
22+
});
23+
it('If error with checked, it should return ERROR_SELECTED state', () => {
24+
const checked = true;
25+
const error = true;
26+
expect(getState(checked, disabled, error, state)).toBe(RadioButtonStateType.ERROR_SELECTED);
27+
});
28+
it('If checked, it should return SELECTED state', () => {
29+
const checked = true;
30+
expect(getState(checked, disabled, error, state)).toBe(RadioButtonStateType.SELECTED);
31+
});
32+
it('If not checked, it should return DEFAULT state', () => {
33+
expect(getState(checked, disabled, error, state)).toBe(RadioButtonStateType.DEFAULT);
34+
});
35+
36+
it('If checked, it should return isChecked true', () => {
37+
const { isChecked, isDisabled, hasError } = radioButtonState(RadioButtonStateType.SELECTED);
38+
expect(isChecked).toBeTruthy();
39+
expect(isDisabled).toBeFalsy();
40+
expect(hasError).toBeFalsy();
41+
});
42+
it('If SELECTED, it should return isChecked true, isDisabled false and hasError false', () => {
43+
const { isChecked, isDisabled, hasError } = radioButtonState(RadioButtonStateType.SELECTED);
44+
expect(isChecked).toBeTruthy();
45+
expect(isDisabled).toBeFalsy();
46+
expect(hasError).toBeFalsy();
47+
});
48+
it('If DISABLED_SELECTED, it should return isChecked true, isDisabled true and hasError false', () => {
49+
const { isChecked, isDisabled, hasError } = radioButtonState(
50+
RadioButtonStateType.DISABLED_SELECTED
51+
);
52+
expect(isChecked).toBeTruthy();
53+
expect(isDisabled).toBeTruthy();
54+
expect(hasError).toBeFalsy();
55+
});
56+
it('If DISABLED, it should return isChecked false, isDisabled true and hasError false', () => {
57+
const { isChecked, isDisabled, hasError } = radioButtonState(RadioButtonStateType.DISABLED);
58+
expect(isChecked).toBeFalsy();
59+
expect(isDisabled).toBeTruthy();
60+
expect(hasError).toBeFalsy();
61+
});
62+
it('If ERROR_SELECTED, it should return isChecked true, isDisabled false and hasError true', () => {
63+
const { isChecked, isDisabled, hasError } = radioButtonState(
64+
RadioButtonStateType.ERROR_SELECTED
65+
);
66+
expect(isChecked).toBeTruthy();
67+
expect(isDisabled).toBeFalsy();
68+
expect(hasError).toBeTruthy();
69+
});
70+
it('If ERROR, it should return isChecked false, isDisabled false and hasError true', () => {
71+
const { isChecked, isDisabled, hasError } = radioButtonState(RadioButtonStateType.ERROR);
72+
expect(isChecked).toBeFalsy();
73+
expect(isDisabled).toBeFalsy();
74+
expect(hasError).toBeTruthy();
75+
});
76+
});

src/components/radioButtonGroup/components/radioButton/radioButton.styled.ts

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,35 @@ interface IRadioButtonContentStyled {
1717
hasLabel?: boolean;
1818
}
1919

20+
interface IRadioButtonErrorStyled {
21+
styles?: RadioButtonBaseStyles;
22+
}
23+
2024
const getDynamicStyles = (styles?: RadioButtonBaseStyles) => css`
2125
${getStyles(styles?.radioButton)}
2226
2327
&:before {
28+
content: '';
29+
left: 50%;
30+
position: absolute;
31+
top: 50%;
32+
transform: translate(-50%, -50%);
2433
${getStyles(styles?.icon)}
2534
}
2635
`;
2736

2837
export const RadioButtonContainerInput = styled.div<IRadioButtonStyled>`
2938
${props => getStyles(props.styles[RadioButtonStateType.DEFAULT]?.radioButtonContainer)}
3039
`;
40+
3141
export const RadioButtonInputStyled = styled.input<IRadioButtonStyled>`
3242
appearance: none;
3343
cursor: pointer;
3444
display: inline-block;
3545
vertical-align: bottom;
3646
position: relative;
3747
38-
${({ styles }) => getDynamicStyles(styles[RadioButtonStateType.DEFAULT])}
39-
40-
// Check - Centering & sizing
41-
&:before {
42-
content: '';
43-
left: 50%;
44-
position: absolute;
45-
top: 50%;
46-
transform: translate(-50%, -50%);
47-
}
48-
49-
&:checked:not(:disabled) {
50-
${({ styles }) => getDynamicStyles(styles[RadioButtonStateType.SELECTED])}
51-
}
52-
53-
&:disabled {
54-
cursor: not-allowed;
55-
${({ styles }) => getDynamicStyles(styles[RadioButtonStateType.DISABLED])}
56-
57-
:checked {
58-
${({ styles }) => getDynamicStyles(styles[RadioButtonStateType.DISABLED_SELECTED])}
59-
}
60-
}
48+
${({ styles, state }) => state && getDynamicStyles(styles?.[state])}
6149
6250
&:focus-visible {
6351
border-radius: 100%;
@@ -82,3 +70,14 @@ export const RadioButtonLabelStyled = styled.div`
8270
display: flex;
8371
align-items: center;
8472
`;
73+
74+
export const RadioButtonErrorStyled = styled.div<IRadioButtonErrorStyled>`
75+
${({ styles }) => getStyles(styles?.errorMessageContainer)};
76+
p {
77+
display: flex;
78+
}
79+
`;
80+
81+
export const ErrorIconWrapperStyled = styled.span<IRadioButtonErrorStyled>`
82+
${({ styles }) => getStyles(styles?.errorMessageIconContainer)};
83+
`;

src/components/radioButtonGroup/components/radioButton/radioButton.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,35 @@ import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary';
66
import { VariantStyles } from '@/types/variantStyles';
77

88
import { RadioButtonGroupStylesType } from '../../types';
9+
import { getState, radioButtonState } from '../../utils';
910
import { RadioButtonStandAlone } from './radioButtonStandAlone';
1011
import { IRadioButton, IRadioButtonStandAlone } from './types';
1112

1213
const STYLES_NAME = 'RADIO_BUTTON_GROUP_STYLES';
1314

1415
const RadioButtonComponent = ({
1516
checked = false,
17+
error = false,
18+
disabled = false,
1619
styles: propsStyles,
20+
state: propsState,
1721
...props
1822
}: IRadioButton): JSX.Element => {
1923
const styles = useStyles<VariantStyles<RadioButtonGroupStylesType>>(STYLES_NAME, props.variant);
24+
const state = getState(checked, disabled, error, propsState);
25+
// deprecated - use only `error`, `disabled` and `checked` props when `state` prop is removed from `RadioButton`
26+
const { isChecked, isDisabled, hasError } = radioButtonState(state);
2027

21-
return <RadioButtonStandAlone checked={checked} styles={propsStyles ?? styles} {...props} />;
28+
return (
29+
<RadioButtonStandAlone
30+
checked={isChecked}
31+
disabled={isDisabled}
32+
error={hasError}
33+
state={state}
34+
styles={propsStyles ?? styles}
35+
{...props}
36+
/>
37+
);
2238
};
2339

2440
export const RadioButton = (props: IRadioButton): JSX.Element => (

0 commit comments

Comments
 (0)