Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Adding isError property in Checkbox #585

Merged
merged 12 commits into from
Feb 5, 2024
17 changes: 16 additions & 1 deletion example/src/components/FormCheckboxDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FormCheckbox, CheckboxGroup } from '@capgeminiuk/dcx-react-library';

export const FormCheckboxDemo = () => {
const [value, setValue] = React.useState('');
const [checked, setChecked] = React.useState<boolean>(true);
const [checked, setChecked] = React.useState<boolean>(false);
const handleChange = (event: any) => {
setValue(event.currentTarget.value);
setChecked(!checked);
Expand Down Expand Up @@ -47,6 +47,21 @@ export const FormCheckboxDemo = () => {
onChange={handleChange}
disabled={true}
/>
<h2 id="checkbox-with-error-tag">Checkbox with Error</h2>
<FormCheckbox
ariaDescribedBy="checkbox-with-error-tag"
id="checkbox-4"
name="group4"
value={value}
label="Checkbox 4 label text"
onChange={handleChange}
isError={!checked}
/>
{checked && (
<div style={{ color: 'red', marginTop: '8px' }}>
Error: The checkbox is checked!
</div>
)}
<h2>Group</h2>
<CheckboxGroup
name="name-of-group"
Expand Down
146 changes: 146 additions & 0 deletions rollup.config-1706545646874.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var postcss = require('rollup-plugin-postcss');
var postcssFunctions = require('postcss-functions');
var postcssImport = require('postcss-import');
var postcssNesting = require('postcss-nesting');
var cssnano = require('cssnano');
var cssnanoPreset = require('cssnano-preset-default');
var fs = require('fs');
var glob = require('glob');
var path = require('path');

function token(name) {
const tokens = fs.readFileSync('./src/design-system/tokens.json', 'utf8');
const parsedTokens = JSON.parse(tokens);
const parsedName = name.replace(/('|")/g, '');
return `var(--dcx-${parsedName}, ${parsedTokens[parsedName]})`;
}

var customFunctions = /*#__PURE__*/Object.freeze({
__proto__: null,
token: token
});

const INPUT_FOLDER = 'src/design-system';
const OUTPUT_FOLDER = 'dist/design-system';

/**
* Cleanup dist folder
*/
const distPath = path.join(__dirname, OUTPUT_FOLDER);
if (fs.existsSync(distPath)) {
fs.rmSync(distPath, { recursive: true });
}

function generateTokens() {
const presenters = {
color: 'Color'
};
const getHeading = (name) => {
const presenter = presenters[name] ? `\t * @presenter ${presenters[name]}\n` : '';
return '\t/**\n' +
`\t * @tokens ${name}\n` +
presenter +
'\t */\n';
};
return {
name: 'copy-file',
transform(code, id) {
return `export const tokens = ${code};`;
},
generateBundle(opts, bundle) {
const files = new Map();
for (const file in bundle) {
const bundleEntry = bundle[file];
files.set(bundleEntry.fileName, bundleEntry.code);
}

Array.from(files.entries())
.forEach(([fileName, code]) => {
const tokensJson = code
.replace('const tokens = ', '')
.replace('export { tokens };', '')
.replace(';', '')
.trim();
const parsedTokens = JSON.parse(tokensJson);

const tokenKeys = Object.keys(parsedTokens)
.sort();

const tokenKeysByCategory = new Map();
tokenKeys.forEach(token => {
const parts = token.split('-');
if (!tokenKeysByCategory.get(parts[0])) {
tokenKeysByCategory.set(parts[0], []);
}
tokenKeysByCategory.get(parts[0]).push(token);
});

let source = ':root {\n';
Array.from(tokenKeysByCategory.entries())
.forEach(([category, tokenKeys]) => {
const cssTokens = tokenKeys
.map((tokenKey) => `\t--dcx-${tokenKey}: ${parsedTokens[tokenKey]};\n`)
.join('');
source = source +
getHeading(category) +
cssTokens;
});
source = source + '};';

this.emitFile({ type: 'asset', fileName, source });
});
}
};
}


const getBaseConfig = () => ({
input: `${INPUT_FOLDER}/input.css.json`,
output: {
file: `${OUTPUT_FOLDER}/input.css`,
format: 'es'
},
plugins: [
postcss({
modules: false,
extract: true,
plugins: [
postcssImport(),
postcssNesting(),
postcssFunctions({
functions: {
...customFunctions
}
}),
cssnano(cssnanoPreset())
]
})
]
});

const config = glob.sync(`${INPUT_FOLDER}/*.css`).reduce((acc, file) => {
acc.push({
...getBaseConfig(),
input: file,
output: {
file: file.replace(`${INPUT_FOLDER}/`, `${OUTPUT_FOLDER}/`),
format: 'es'
}
});
return acc;
}, [
{
input: `${INPUT_FOLDER}/tokens.json`,
output: {
file: `${OUTPUT_FOLDER}/tokens.css`,
format: 'es'
},
plugins: [generateTokens(),]
}
]);

exports.default = config;
4 changes: 4 additions & 0 deletions src/common/components/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ export type FormRadioCheckboxProps = {
* specifies an optional className for the item
*/
itemClassName?: string;
/**
* specifies whether there is an error with the input.
*/
isError?: boolean;
};

export type HintProps = {
Expand Down
34 changes: 27 additions & 7 deletions src/formCheckbox/FormCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { FormRadioCheckboxProps } from '../common/components/commonTypes';
import { CheckboxRadioBase, Roles } from '../common';
import { classNames } from '../common/utils';

export const FormCheckbox = ({
id,
Expand All @@ -23,10 +24,28 @@ export const FormCheckbox = ({
inputClassName,
labelClassName,
itemClassName,
isError
}: FormRadioCheckboxProps & {
onChange?: (event: React.ChangeEvent, conditional?: string) => void;
}) => (
<CheckboxRadioBase
isError?: boolean;
}) => {
const containerClasses = classNames([
itemClassName,
{ 'dcx-checkbox-container--error': isError },
]);

const checkboxClasses = classNames([
inputClassName,
{ 'dcx-checkbox-checkbox--error': isError },
]);

const labelClasses = classNames([
labelClassName,
{ 'dcx-checkbox-label--error': isError },
]);

return (
<CheckboxRadioBase
type="checkbox"
id={id}
role={Roles.formCheckbox}
Expand All @@ -46,8 +65,9 @@ export const FormCheckbox = ({
selected={selected}
hint={hint}
nested={nested}
inputClassName={inputClassName}
labelClassName={labelClassName}
itemClassName={itemClassName}
/>
);
itemClassName={containerClasses}
inputClassName={checkboxClasses}
labelClassName={labelClasses}
/>
)
}
68 changes: 65 additions & 3 deletions src/formCheckbox/__test__/FormCheckbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ describe('FormCheckbox', () => {

const label: any = container.querySelector('#my-label');

expect(label.className).toBe('my-label-class');
expect(label.className.trim()).toBe('my-label-class');
});

it('should style the checkbox item', () => {
Expand All @@ -450,7 +450,7 @@ describe('FormCheckbox', () => {

const checkbox: any = container.querySelector('#checkbox-item');

expect(checkbox.className).toBe('my-checkbox-class');
expect(checkbox.className.trim()).toBe('my-checkbox-class');
});

it('should style the checkbox input', () => {
Expand All @@ -471,7 +471,7 @@ describe('FormCheckbox', () => {

const input: any = container.querySelector('#input-item');

expect(input.className).toBe('my-input-class');
expect(input.className.trim()).toBe('my-input-class');
});


Expand All @@ -495,4 +495,66 @@ describe('FormCheckbox', () => {
const firstItemEl: any = screen.getByRole('link');
expect(firstItemEl.href).toBe('http://localhost/link');
});

it('should apply error styling when isError is true', () => {
const handleChange = jest.fn();

const { container } = render(
<FormCheckbox
id="myId"
name="group1"
value="choice 1"
label="my label"
onChange={handleChange}
isError={true}
/>
);

const checkboxContainer = container.querySelector('.dcx-checkbox-container--error');
const checkbox = container.querySelector('.dcx-checkbox-checkbox--error');
const label = container.querySelector('.dcx-checkbox-label--error');

expect(checkboxContainer).toBeInTheDocument();
expect(checkbox).toBeInTheDocument();
expect(label).toBeInTheDocument();

});

it('should not apply error styling when isError is false', () => {
const handleChange = jest.fn();

const { container } = render(
<FormCheckbox
id="myId"
name="group1"
value="choice 1"
label="my label"
onChange={handleChange}
isError={false}
/>
);

expect(container.querySelector('.dcx-checkbox-container--error')).toBeNull();
expect(container.querySelector('.dcx-checkbox-checkbox--error')).toBeNull();
expect(container.querySelector('.dcx-checkbox-label--error')).toBeNull();
});

it('should not apply error styling when isError is not provided', () => {
const handleChange = jest.fn();

const { container } = render(
<FormCheckbox
id="myId"
name="group1"
value="choice 1"
label="my label"
onChange={handleChange}
/>
);

expect(container.querySelector('.dcx-checkbox-container--error')).toBeNull();
expect(container.querySelector('.dcx-checkbox-checkbox--error')).toBeNull();
expect(container.querySelector('.dcx-checkbox-label--error')).toBeNull();
});

});
4 changes: 3 additions & 1 deletion stories/FormCheckbox/ClassBased.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FormCheckbox } from '../../src/formCheckbox/FormCheckbox';
import { useArgs } from '@storybook/preview-api';
import '../govUkStyle.css'

/**
* In this section we're using the checkbox component providing the **GovUk style** passing the relative `className.
Expand Down Expand Up @@ -289,4 +290,5 @@ export const SmallCheckbox = {
defaultChecked:false,
},
argTypes: { onChange: { action: 'changed' } },
};
};

1 change: 1 addition & 0 deletions stories/FormCheckbox/Documentation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ An example with all the available properties is:
inputClassName="inputClassName"
labelClassName="labelClassName"
itemClassName="itemClassName"
isError={true}
/>
```

Expand Down
9 changes: 7 additions & 2 deletions stories/FormCheckbox/UnStyled.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export const Unstyled = {
console.log(conditional);
return;
}
setArgs({ value: evt.currentTarget.value, selected: evt.currentTarget.checked});
setArgs({
value: evt.currentTarget.value,
selected: evt.currentTarget.checked,
isError: true
});
setTimeout(() => setArgs({ isLoading: false }), 2000);
};
return <FormCheckbox {...args} onChange={checkboxHandler} />;
Expand All @@ -40,7 +44,8 @@ export const Unstyled = {
type: 'text',
id: 'checkbox-6',
inputId: 'input-6',
}
},
isError: false
},
argTypes: { onChange: { action: 'changed' } },
};
Loading