Skip to content

Commit

Permalink
Replace checkboxes and radiobuttons with new components from the desi…
Browse files Browse the repository at this point in the history
…gn system (#941)

* add checkboxgroup from DS and start on radiogroup

* update radio and checkboxgroups

* implement component from digdir-designsystem

* update likert, radio and checkboxgroup

* add helpText to options

* fix tests for likert, checkbox and radiogroup

* update styles and remove unused component

* update class names

* fix likert css

* get radiobutton from digdir ds

* update checkboxgroup

* fix cypress tests

* clean up cypress tests

* fix cypress test

* break up action chains

* fix cypresss tests

* get radios by type radio

* test blurring after check in navigation

* add header to label for screenreader accessibility

* update designsystem

* fix screenreader issue with likert component

* make texts optional

* fix cypress tests

* revert change

* fix group cyress test

* fix cypress

* cypress fix

* edit comment

* fix cypress summary test

* revert waiting for summary

* linting

* test

* attempt fix cypress test

* clean up cypress test

* linting

* wait on intercept

* update cypress test

* remove unused css

* fix WCAG issue aria-labelledby

* enable rich text in labels

* make helptext string

* update designsystem

* remove unused property
  • Loading branch information
Magnusrm authored Mar 21, 2023
1 parent 6330a6c commit 3c9da64
Show file tree
Hide file tree
Showing 32 changed files with 291 additions and 421 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
"@altinn/altinn-design-system": "0.27.6",
"@babel/polyfill": "7.12.1",
"@date-io/moment": "1.3.13",
"@digdir/design-system-react": "0.8.1",
"@digdir/design-system-react": "0.9.0",
"@material-ui/core": "4.12.4",
"@material-ui/icons": "4.11.3",
"@material-ui/pickers": "3.3.10",
Expand Down
7 changes: 1 addition & 6 deletions src/features/form/components/Legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,7 @@ export function Legend(props: IFormLegendProps) {
return (
<>
<div className={classes.legendHelpTextContainer}>
<label
className='a-form-label title-label'
htmlFor={props.id}
>
{LabelText}
</label>
<legend className='a-form-label title-label'>{LabelText}</legend>
{props.helpText && (
<HelpTextContainer
language={props.language}
Expand Down
2 changes: 1 addition & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ option {
margin-bottom: 0;
}

.a-form-label.title-label .label-optional {
.label-optional {
font-size: 0.875rem;
font-weight: 400;
color: #6a6a6a;
Expand Down
19 changes: 4 additions & 15 deletions src/layout/Checkboxes/CheckboxesContainerComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,7 @@ describe('CheckboxContainerComponent', () => {
});

// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root').length).toBe(1);
// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root.MuiFormGroup-row').length).toBe(1);
expect(container.querySelector('fieldset > div')).toHaveStyle('flex-direction: row;');
});

it('should show items in a row when layout is not defined, and options count is 2', () => {
Expand All @@ -308,10 +306,7 @@ describe('CheckboxContainerComponent', () => {
});

// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root').length).toBe(1);

// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root.MuiFormGroup-row').length).toBe(1);
expect(container.querySelector('fieldset > div')).toHaveStyle('flex-direction: row;');
});

it('should show items in a column when layout is "column" and options count is 2 ', () => {
Expand All @@ -331,10 +326,7 @@ describe('CheckboxContainerComponent', () => {
});

// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root').length).toBe(1);

// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root.MuiFormGroup-row').length).toBe(0);
expect(container.querySelector('fieldset > div')).toHaveStyle('flex-direction: column;');
});

it('should show items in a columns when layout is not defined, and options count is 3', () => {
Expand All @@ -345,10 +337,7 @@ describe('CheckboxContainerComponent', () => {
});

// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root').length).toBe(1);

// eslint-disable-next-line
expect(container.querySelectorAll('.MuiFormGroup-root.MuiFormGroup-row').length).toBe(0);
expect(container.querySelector('fieldset > div')).toHaveStyle('flex-direction: column;');
});

it('should present replaced label if setup with values from repeating group in redux and trigger handleDataChanged with replaced values', async () => {
Expand Down
209 changes: 69 additions & 140 deletions src/layout/Checkboxes/CheckboxesContainerComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,94 +1,46 @@
import React from 'react';

import { FormControlLabel, FormGroup, FormLabel } from '@material-ui/core';
import Checkbox from '@material-ui/core/Checkbox';
import FormControl from '@material-ui/core/FormControl';
import { makeStyles } from '@material-ui/core/styles';
import cn from 'classnames';
import type { CheckboxProps } from '@material-ui/core/Checkbox';
import { CheckboxGroup, CheckboxGroupVariant } from '@digdir/design-system-react';

import { useHasChangedIgnoreUndefined } from 'src/common/hooks';
import { useAppSelector } from 'src/common/hooks/useAppSelector';
import { AltinnSpinner } from 'src/components/AltinnSpinner';
import { useGetOptions } from 'src/components/hooks';
import { useDelayedSavedState } from 'src/components/hooks/useDelayedSavedState';
import { OptionalIndicator } from 'src/features/form/components/OptionalIndicator';
import { RequiredIndicator } from 'src/features/form/components/RequiredIndicator';
import { shouldUseRowLayout } from 'src/utils/layout';
import { getOptionLookupKey } from 'src/utils/options';
import type { PropsFromGenericComponent } from 'src/layout';
import type { IOption } from 'src/types';

export type ICheckboxContainerProps = PropsFromGenericComponent<'Checkboxes'>;

interface IStyledCheckboxProps extends CheckboxProps {
label: string;
}

const useStyles = makeStyles((theme) => ({
root: {
'&:hover': {
backgroundColor: 'transparent',
},
},
icon: {
border: `2px solid ${theme.altinnPalette.primary.blueMedium}`,
width: 24,
height: 24,
backgroundColor: '#ffffff',
'$root.Mui-focusVisible &': {
outline: '2px solid #ff0000',
outlineOffset: 0,
outlineColor: theme.altinnPalette.primary.blueDark,
},
'input:hover ~ &': {
borderColor: theme.altinnPalette.primary.blueDark,
},
'input:disabled ~ &': {
boxShadow: 'none',
background: 'rgba(206,217,224,.5)',
},
},
checkedIcon: {
backgroundColor: '#ffffff',
'&:before': {
display: 'block',
width: 20,
height: 20,

backgroundImage:
"url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23000000' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E\")",
content: '""',
},
'input:hover ~ &': {
borderColor: theme.altinnPalette.primary.blueDark,
},
},
legend: {
color: '#000000',
fontFamily: 'Altinn-DIN',
},
formControl: {
alignItems: 'flex-start',
marginBottom: '0.75rem',
wordBreak: 'break-word',
'& > span:last-child': {
marginTop: 9,
},
},
}));

const defaultOptions: IOption[] = [];
const defaultSelectedOptions: string[] = [];

export const CheckboxContainerComponent = ({
node,
formData,
text,
isValid,
language,
handleDataChange,
legend,
getTextResourceAsString,
getTextResource,
}: ICheckboxContainerProps) => {
const classes = useStyles();
const { id, options, optionsId, preselectedOptionIndex, layout, readOnly, mapping, source } = node.item;
const {
id,
options,
optionsId,
preselectedOptionIndex,
layout,
readOnly,
mapping,
source,
textResourceBindings,
required,
labelSettings,
} = node.item;
const apiOptions = useGetOptions({ optionsId, mapping, source });
const calculatedOptions = apiOptions || options || defaultOptions;
const hasSelectedInitial = React.useRef(false);
Expand Down Expand Up @@ -122,14 +74,10 @@ export const CheckboxContainerComponent = ({
}
}, [setValue, optionsHasChanged, formData]);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const clickedItem = event.target.name;
const isSelected = isOptionSelected(clickedItem);

if (isSelected) {
setValue(selected.filter((x) => x !== clickedItem).join(','));
} else {
setValue(selected.concat(clickedItem).join(','));
const handleChange = (checkedItems: string[]) => {
const checkedItemsString = checkedItems.join(',');
if (checkedItemsString !== value) {
setValue(checkedItems.join(','));
}
};

Expand All @@ -140,72 +88,53 @@ export const CheckboxContainerComponent = ({
}
};

const isOptionSelected = (option: string) => selected.includes(option);

const RenderLegend = legend;

return (
<FormControl
key={`checkboxes_control_${id}`}
component='fieldset'
>
<FormLabel
component='legend'
classes={{ root: cn(classes.legend) }}
>
<RenderLegend />
</FormLabel>
<FormGroup
row={shouldUseRowLayout({
layout,
optionsCount: calculatedOptions.length,
})}
id={id}
key={`checkboxes_group_${id}`}
onBlur={handleBlur}
>
{fetchingOptions ? (
<AltinnSpinner />
) : (
<>
{calculatedOptions.map((option, index) => (
<FormControlLabel
tabIndex={-1}
key={option.value}
classes={{ root: cn(classes.formControl) }}
disabled={readOnly}
control={
<StyledCheckbox
checked={isOptionSelected(option.value)}
onChange={handleChange}
value={index}
key={option.value}
name={option.value}
label={getTextResourceAsString(option.label)}
/>
}
label={getTextResource(option.label)}
/>
))}
</>
)}
</FormGroup>
</FormControl>
const labelText = (
<span>
{text}
<RequiredIndicator
required={required}
language={language}
/>
<OptionalIndicator
labelSettings={labelSettings}
language={language}
required={required}
/>
</span>
);
};

const StyledCheckbox = ({ label, ...rest }: IStyledCheckboxProps) => {
const classes = useStyles();

return (
<Checkbox
className={classes.root}
disableRipple={true}
color='default'
checkedIcon={<span className={cn(classes.icon, classes.checkedIcon)} />}
icon={<span className={classes.icon} />}
inputProps={{ 'aria-label': label }}
{...rest}
/>
return fetchingOptions ? (
<AltinnSpinner />
) : (
<div
id={id}
key={`checkboxes_group_${id}`}
onBlur={handleBlur}
>
<CheckboxGroup
compact={false}
disabled={readOnly}
onChange={(values) => handleChange(values)}
legend={labelText}
description={textResourceBindings?.description && getTextResource(textResourceBindings.description)}
error={!isValid}
helpText={textResourceBindings?.help && getTextResource(textResourceBindings.help)}
variant={
shouldUseRowLayout({
layout,
optionsCount: calculatedOptions.length,
})
? CheckboxGroupVariant.Horizontal
: CheckboxGroupVariant.Vertical
}
items={calculatedOptions.map((option) => ({
name: option.value,
checkboxId: `${id}-${option.label.replace(/\s/g, '-')}`,
checked: selected.includes(option.value),
label: getTextResource(option.label),
helpText: option.helpText && getTextResource(option.helpText),
}))}
/>
</div>
);
};
1 change: 1 addition & 0 deletions src/layout/GenericComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export function GenericComponent<Type extends ComponentTypes = ComponentTypes>({
language,
shouldFocus,
text: texts.title,
texts,
label: RenderLabel,
legend: RenderLegend,
componentValidations,
Expand Down
24 changes: 15 additions & 9 deletions src/layout/Likert/GroupContainerLikert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,11 @@ describe('GroupContainerLikert', () => {
it('should render mobile view and click radiobuttons', async () => {
const { mockStoreDispatch } = render({ mobileView: true });
validateRadioLayout(defaultMockQuestions, true);
const rad1 = screen.getByRole('radiogroup', {
name: /Hvordan trives du på skolen/i,
});
const rad1 = within(
screen.getByRole('group', {
name: /Hvordan trives du på skolen/i,
}),
).getByRole('radiogroup');
const btn1 = within(rad1).getByRole('radio', {
name: /Bra/i,
});
Expand All @@ -244,9 +246,11 @@ describe('GroupContainerLikert', () => {
expect(mockStoreDispatch).toHaveBeenCalledWith(createFormDataUpdateAction(0, '1'));
mockStoreDispatch.mockClear();

const rad2 = screen.getByRole('radiogroup', {
name: /Har du det bra/i,
});
const rad2 = within(
screen.getByRole('group', {
name: /Har du det bra/i,
}),
).getByRole('radiogroup');

const btn2 = within(rad2).getByRole('radio', {
name: /Dårlig/i,
Expand All @@ -269,9 +273,11 @@ describe('GroupContainerLikert', () => {
validateRadioLayout(questions, true);

// Validate that radio is selected
const selectedRow = screen.getByRole('radiogroup', {
name: questions[2].Question,
});
const selectedRow = within(
screen.getByRole('group', {
name: questions[2].Question,
}),
).getByRole('radiogroup');

const selectedRadio = within(selectedRow).getByRole('radio', {
name: /Ok/i,
Expand Down
Loading

0 comments on commit 3c9da64

Please sign in to comment.