Skip to content

Commit

Permalink
React nodes in checkboxes and radio buttons (#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasEng authored Jan 9, 2023
1 parent 6ec8cc8 commit 7314361
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 21 deletions.
25 changes: 25 additions & 0 deletions src/components/Checkbox/Checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,31 @@ describe('Checkbox', () => {
render({ description });
expect(screen.getByText(description)).toBeDefined();
});

it.each([false, undefined])(
'Does not have presentation role when the "presentation" property is %s',
(presentation) => {
render({ presentation });
expect(screen.queryByRole('presentation')).toBeFalsy();
},
);

it('Has presentation role when the "presentation" property is true', () => {
render({ presentation: true });
expect(screen.getByRole('presentation')).toBeInTheDocument();
expect(screen.queryByRole('checkbox')).toBeFalsy();
});

it('Displays label and description when they are React nodes', () => {
const labelText = 'Label';
const descriptionText = 'Description';
render({
label: <span>{labelText}</span>,
description: <span>{descriptionText}</span>,
});
expect(screen.getByText(labelText)).toBeInTheDocument();
expect(screen.getByText(descriptionText)).toBeInTheDocument();
});
});

const render = (props: Partial<CheckboxProps> = {}) => {
Expand Down
9 changes: 6 additions & 3 deletions src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ChangeEventHandler } from 'react';
import type { ChangeEventHandler, ReactNode } from 'react';
import React from 'react';
import cn from 'classnames';

Expand All @@ -13,13 +13,14 @@ export interface CheckboxProps {
checkboxId?: string;
checked?: boolean;
compact?: boolean;
description?: string;
description?: ReactNode;
disabled?: boolean;
error?: boolean;
hideLabel?: boolean;
label?: string;
label?: ReactNode;
name?: string;
onChange: ChangeEventHandler<HTMLInputElement>;
presentation?: boolean;
readOnly?: boolean;
}

Expand All @@ -34,6 +35,7 @@ export const Checkbox = ({
label,
name,
onChange,
presentation,
readOnly,
}: CheckboxProps) => (
<CheckboxRadioTemplate
Expand All @@ -54,6 +56,7 @@ export const Checkbox = ({
label={label}
name={name}
onChange={onChange}
presentation={presentation}
size={
compact
? CheckboxRadioTemplateSize.Xsmall
Expand Down
7 changes: 7 additions & 0 deletions src/components/CheckboxGroup/CheckboxGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ describe('CheckboxGroup', () => {
expect(screen.getByText(label)).toBeDefined();
expect(screen.getByText(description)).toBeDefined();
});

it('Renders all checkboxes with presentation role when the "presentation" property is true', () => {
render({ presentation: true });
const numOfCheckboxes = defaultProps.items.length;
expect(screen.queryAllByRole('presentation')).toHaveLength(numOfCheckboxes);
expect(screen.queryAllByRole('checkbox')).toHaveLength(0);
});
});

const render = (props: Partial<CheckboxGroupProps> = {}) => {
Expand Down
10 changes: 7 additions & 3 deletions src/components/CheckboxGroup/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ReactNode } from 'react';
import React, { useEffect, useReducer } from 'react';
import cn from 'classnames';

Expand Down Expand Up @@ -25,12 +26,13 @@ export type CheckedNames = string[];

export interface CheckboxGroupProps {
compact?: boolean;
description?: string;
description?: ReactNode;
disabled?: boolean;
error?: React.ReactNode;
error?: ReactNode;
items: CheckboxItem[];
legend?: string;
legend?: ReactNode;
onChange?: (names: CheckedNames) => void;
presentation?: boolean;
variant?: CheckboxGroupVariant;
}

Expand Down Expand Up @@ -59,6 +61,7 @@ export const CheckboxGroup = ({
items,
legend,
onChange,
presentation,
variant = CheckboxGroupVariant.Vertical,
}: CheckboxGroupProps) => {
if (!areItemsUnique(items.map((item) => item.name))) {
Expand Down Expand Up @@ -113,6 +116,7 @@ export const CheckboxGroup = ({
name: item.name,
});
}}
presentation={presentation}
/>
))}
</div>
Expand Down
11 changes: 11 additions & 0 deletions src/components/FieldSet/FieldSet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ describe('FieldSet', () => {
render({ disabled: false });
expect(screen.getByRole('group')).toBeEnabled();
});

it('Displays legend and description if they are React nodes', () => {
const legendText = 'Legend';
const descriptionText = 'Description';
render({
legend: <span>{legendText}</span>,
description: <span>{descriptionText}</span>,
});
expect(screen.getByText(legendText)).toBeInTheDocument();
expect(screen.getByText(descriptionText)).toBeInTheDocument();
});
});

const render = (props: Partial<FieldSetProps> = {}) => {
Expand Down
9 changes: 5 additions & 4 deletions src/components/FieldSet/FieldSet.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ReactNode } from 'react';
import React from 'react';
import cn from 'classnames';

Expand All @@ -6,12 +7,12 @@ import { ErrorMessage } from '@/components/ErrorMessage';
import classes from './FieldSet.module.css';

export interface FieldSetProps {
children: React.ReactNode;
children: ReactNode;
className?: string;
description?: string;
description?: ReactNode;
disabled?: boolean;
error?: React.ReactNode;
legend?: string;
error?: ReactNode;
legend?: ReactNode;
size?: FieldSetSize;
}

Expand Down
25 changes: 25 additions & 0 deletions src/components/RadioButton/RadioButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,31 @@ describe('RadioButton', () => {
const wrapper = renderAndGetWrapper({ disabled: false });
expect(wrapper).not.toHaveClass('radio--disabled');
});

it.each([false, undefined])(
'Does not have presentation role when the "presentation" property is %s',
(presentation) => {
render({ presentation });
expect(screen.queryByRole('presentation')).toBeFalsy();
},
);

it('Has presentation role when the "presentation" property is true', () => {
render({ presentation: true });
expect(screen.getByRole('presentation')).toBeInTheDocument();
expect(screen.queryByRole('radio')).toBeFalsy();
});

it('Displays label and description when they are React nodes', () => {
const labelText = 'Label';
const descriptionText = 'Description';
render({
label: <span>{labelText}</span>,
description: <span>{descriptionText}</span>,
});
expect(screen.getByText(labelText)).toBeInTheDocument();
expect(screen.getByText(descriptionText)).toBeInTheDocument();
});
});

const render = (props: Partial<RadioButtonProps> = {}) => {
Expand Down
9 changes: 6 additions & 3 deletions src/components/RadioButton/RadioButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ChangeEventHandler } from 'react';
import type { ChangeEventHandler, ReactNode } from 'react';
import React from 'react';
import cn from 'classnames';

Expand All @@ -17,13 +17,14 @@ export enum RadioButtonSize {
export interface RadioButtonProps {
checked?: boolean;
className?: string;
description?: string;
description?: ReactNode;
disabled?: boolean;
error?: boolean;
hideLabel?: boolean;
label?: string;
label?: ReactNode;
name: string;
onChange: ChangeEventHandler<HTMLInputElement>;
presentation?: boolean;
radioId?: string;
size?: RadioButtonSize;
value: string;
Expand All @@ -38,6 +39,7 @@ export const RadioButton = ({
label,
name,
onChange,
presentation,
radioId,
size = RadioButtonSize.Small,
value,
Expand All @@ -58,6 +60,7 @@ export const RadioButton = ({
label={label}
name={name}
onChange={onChange}
presentation={presentation}
size={
size === RadioButtonSize.Xsmall
? CheckboxRadioTemplateSize.Xsmall
Expand Down
8 changes: 8 additions & 0 deletions src/components/RadioGroup/RadioGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ describe('RadioGroup', () => {
render({ size: RadioGroupSize.Small });
expect(screen.getByRole('radiogroup')).toHaveClass('radio-group--small');
});

it('Renders all radio buttons with presentation role when the "presentation" property is true', () => {
render({ presentation: true });
const numberOfRadios = defaultProps.items.length;
expect(screen.queryAllByRole('presentation')).toHaveLength(numberOfRadios);
expect(screen.queryAllByRole('radio')).toHaveLength(0);
expect(screen.queryAllByRole('radiogroup')).toHaveLength(0);
});
});

const render = (props?: Partial<RadioGroupProps>) =>
Expand Down
13 changes: 8 additions & 5 deletions src/components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ChangeEvent } from 'react';
import type { ChangeEvent, ReactNode } from 'react';
import React, { useEffect, useState } from 'react';

import { RadioButton, RadioButtonSize } from '@/components/RadioButton';
Expand Down Expand Up @@ -26,13 +26,14 @@ export enum RadioGroupVariant {
}

export interface RadioGroupProps {
description?: string;
description?: ReactNode;
disabled?: boolean;
error?: React.ReactNode;
error?: ReactNode;
items: RadioItem[];
legend?: string;
legend?: ReactNode;
name: string;
onChange?: (value?: string) => void;
presentation?: boolean;
size?: RadioGroupSize;
value?: string;
variant?: RadioGroupVariant;
Expand All @@ -46,6 +47,7 @@ export const RadioGroup = ({
legend,
name,
onChange,
presentation,
size = RadioGroupSize.Small,
value,
variant = RadioGroupVariant.Vertical,
Expand Down Expand Up @@ -91,7 +93,7 @@ export const RadioGroup = ({
classes[`radio-group--${variant}`],
classes[`radio-group--${size}`],
].join(' ')}
role='radiogroup'
role={presentation ? undefined : 'radiogroup'}
>
{items.map((radio) => (
<RadioButton
Expand All @@ -102,6 +104,7 @@ export const RadioGroup = ({
key={radio.value}
name={name}
onChange={changeHandler(radio.value)}
presentation={presentation}
size={radioButtonSize}
/>
))}
Expand Down
11 changes: 8 additions & 3 deletions src/components/_CheckboxRadioTemplate/CheckboxRadioTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ export interface CheckboxRadioTemplateProps {
checked?: boolean;
children: ReactNode;
className?: string;
description?: string;
description?: ReactNode;
disabled?: boolean;
hideInput?: boolean;
hideLabel?: boolean;
inputId?: string;
inputWrapperClassName?: string;
label?: string;
label?: ReactNode;
name?: string;
onChange: ChangeEventHandler<HTMLInputElement>;
presentation?: boolean;
size: CheckboxRadioTemplateSize;
type: 'checkbox' | 'radio';
value?: string;
Expand All @@ -44,6 +45,7 @@ export const CheckboxRadioTemplate = ({
label,
name,
onChange,
presentation,
size,
type,
value,
Expand All @@ -68,14 +70,17 @@ export const CheckboxRadioTemplate = ({
<span className={classes['template__input-wrapper']}>
<input
aria-describedby={descriptionId}
aria-label={!showLabel ? label : undefined}
aria-label={
!showLabel && typeof label === 'string' ? label : undefined
}
aria-labelledby={showLabel ? labelId : undefined}
checked={checked ?? false}
className={classes['template__input-wrapper__input']}
disabled={disabled}
id={finalInputId}
name={name}
onChange={disabled ? undefined : onChange}
role={presentation ? 'presentation' : undefined}
type={type}
value={value}
/>
Expand Down

0 comments on commit 7314361

Please sign in to comment.