- Row
+
`;
exports[`SummaryList SummaryList.Value matches snapshot 1`] = `
-
- Value
-
+
+
+ Value
+
+
`;
exports[`SummaryList matches snapshot: SummaryList 1`] = `
-
+
`;
diff --git a/src/components/summary-list/index.ts b/src/components/content-presentation/summary-list/index.ts
similarity index 100%
rename from src/components/summary-list/index.ts
rename to src/components/content-presentation/summary-list/index.ts
diff --git a/src/components/content-presentation/table/Table.tsx b/src/components/content-presentation/table/Table.tsx
new file mode 100644
index 00000000..73942891
--- /dev/null
+++ b/src/components/content-presentation/table/Table.tsx
@@ -0,0 +1,69 @@
+import React, { ComponentProps, FC, HTMLProps, ReactNode, useMemo, useState } from 'react';
+import classNames from 'classnames';
+import TableBody from './components/TableBody';
+import TableCaption from './components/TableCaption';
+import TableCell, { TableCellProps } from './components/TableCell';
+import TableContainer from './components/TableContainer';
+import TableHead from './components/TableHead';
+import TableRow from './components/TableRow';
+import TablePanel, { TablePanelProps } from './components/TablePanel';
+import TableContext, { ITableContext } from './TableContext';
+
+interface TableProps extends HTMLProps
{
+ responsive?: boolean;
+ caption?: ReactNode;
+ captionProps?: ComponentProps;
+}
+
+interface Table extends FC {
+ Body: FC>;
+ Cell: FC;
+ Container: FC>;
+ Head: FC>;
+ Panel: FC;
+ Row: FC>;
+}
+
+const Table = ({
+ caption,
+ captionProps,
+ children,
+ className,
+ responsive = false,
+ ...rest
+}: TableProps) => {
+ const [headings, setHeadings] = useState([]);
+
+ const contextValue: ITableContext = useMemo(() => {
+ return {
+ isResponsive: Boolean(responsive),
+ headings,
+ setHeadings,
+ };
+ }, [responsive, headings, setHeadings]);
+
+ return (
+
+
+ {caption && {caption} }
+ {children}
+
+
+ );
+};
+
+Table.Body = TableBody;
+Table.Cell = TableCell;
+Table.Container = TableContainer;
+Table.Head = TableHead;
+Table.Panel = TablePanel;
+Table.Row = TableRow;
+
+export default Table;
diff --git a/src/components/table/TableContext.ts b/src/components/content-presentation/table/TableContext.ts
similarity index 100%
rename from src/components/table/TableContext.ts
rename to src/components/content-presentation/table/TableContext.ts
diff --git a/src/components/table/TableHelpers.ts b/src/components/content-presentation/table/TableHelpers.ts
similarity index 78%
rename from src/components/table/TableHelpers.ts
rename to src/components/content-presentation/table/TableHelpers.ts
index 8d791ab7..58cd1929 100644
--- a/src/components/table/TableHelpers.ts
+++ b/src/components/content-presentation/table/TableHelpers.ts
@@ -1,4 +1,4 @@
-import React, { isValidElement, ReactElement, ReactNode } from 'react';
+import { Children, isValidElement, ReactElement, ReactNode } from 'react';
import TableCell from './components/TableCell';
export const isTableCell = (child: ReactNode): child is ReactElement => {
@@ -7,7 +7,7 @@ export const isTableCell = (child: ReactNode): child is ReactElement => {
export const getHeadingsFromChildren = (children: ReactNode): string[] => {
const headings: string[] = [];
- React.Children.map(children, (child) => {
+ Children.map(children, (child) => {
if (isTableCell(child)) {
headings.push(child.props.children.toString());
}
diff --git a/src/components/table/TableSectionContext.ts b/src/components/content-presentation/table/TableSectionContext.ts
similarity index 100%
rename from src/components/table/TableSectionContext.ts
rename to src/components/content-presentation/table/TableSectionContext.ts
diff --git a/src/components/table/components/TableBody.tsx b/src/components/content-presentation/table/components/TableBody.tsx
similarity index 71%
rename from src/components/table/components/TableBody.tsx
rename to src/components/content-presentation/table/components/TableBody.tsx
index 110cf9c2..8d6bdf4a 100644
--- a/src/components/table/components/TableBody.tsx
+++ b/src/components/content-presentation/table/components/TableBody.tsx
@@ -1,12 +1,8 @@
import classNames from 'classnames';
-import React, { HTMLProps } from 'react';
+import React, { FC, HTMLProps } from 'react';
import TableSectionContext, { TableSection } from '../TableSectionContext';
-const TableBody: React.FC> = ({
- className,
- children,
- ...rest
-}) => (
+const TableBody: FC> = ({ className, children, ...rest }) => (
{children}
diff --git a/src/components/table/components/TableCaption.tsx b/src/components/content-presentation/table/components/TableCaption.tsx
similarity index 58%
rename from src/components/table/components/TableCaption.tsx
rename to src/components/content-presentation/table/components/TableCaption.tsx
index 0e0427a0..e40a3d98 100644
--- a/src/components/table/components/TableCaption.tsx
+++ b/src/components/content-presentation/table/components/TableCaption.tsx
@@ -1,7 +1,7 @@
-import React, { HTMLProps } from 'react';
+import React, { FC, HTMLProps } from 'react';
import classNames from 'classnames';
-const TableCaption: React.FC> = ({ className, ...rest }) => (
+const TableCaption: FC> = ({ className, ...rest }) => (
);
TableCaption.displayName = 'Table.Caption';
diff --git a/src/components/table/components/TableCell.tsx b/src/components/content-presentation/table/components/TableCell.tsx
similarity index 66%
rename from src/components/table/components/TableCell.tsx
rename to src/components/content-presentation/table/components/TableCell.tsx
index 7b33d818..7e675bea 100644
--- a/src/components/table/components/TableCell.tsx
+++ b/src/components/content-presentation/table/components/TableCell.tsx
@@ -1,6 +1,6 @@
import classNames from 'classnames';
-import React, { HTMLProps, useContext } from 'react';
-import useDevWarning from '../../../util/hooks/UseDevWarning';
+import React, { FC, HTMLProps, useContext } from 'react';
+import useDevWarning from '@util/hooks/UseDevWarning';
import TableSectionContext, { TableSection } from '../TableSectionContext';
const CellOutsideOfSectionWarning =
@@ -12,10 +12,10 @@ export interface TableCellProps extends HTMLProps {
isNumeric?: boolean;
}
-const TableCell: React.FC = ({
+const TableCell: FC = ({
className,
- _responsive,
- _responsiveHeading,
+ _responsive = false,
+ _responsiveHeading = '',
isNumeric,
children,
...rest
@@ -26,36 +26,24 @@ const TableCell: React.FC = ({
const cellClass = section === TableSection.HEAD ? 'nhsuk-table__header' : 'nhsuk-table__cell';
const classes = classNames(cellClass, { [`${cellClass}--numeric`]: isNumeric }, className);
- switch (section) {
- case TableSection.HEAD:
- return (
+ return (
+ <>
+ {section === TableSection.HEAD ? (
{children}
- );
-
- case TableSection.BODY:
- case TableSection.NONE:
- default:
- return (
-
+ ) : (
+
{_responsive && (
{_responsiveHeading}
)}
{children}
- );
- }
+ )}
+ >
+ );
};
TableCell.displayName = 'Table.Cell';
-TableCell.defaultProps = {
- _responsive: false,
- _responsiveHeading: '',
-};
export default TableCell;
diff --git a/src/components/table/components/TableContainer.tsx b/src/components/content-presentation/table/components/TableContainer.tsx
similarity index 60%
rename from src/components/table/components/TableContainer.tsx
rename to src/components/content-presentation/table/components/TableContainer.tsx
index 4254bc2b..f47cb3a1 100644
--- a/src/components/table/components/TableContainer.tsx
+++ b/src/components/content-presentation/table/components/TableContainer.tsx
@@ -1,7 +1,7 @@
-import React, { HTMLProps } from 'react';
+import React, { FC, HTMLProps } from 'react';
import classNames from 'classnames';
-const TableContainer: React.FC> = ({ className, ...rest }) => (
+const TableContainer: FC> = ({ className, ...rest }) => (
);
TableContainer.displayName = 'Table.Container';
diff --git a/src/components/table/components/TableHead.tsx b/src/components/content-presentation/table/components/TableHead.tsx
similarity index 71%
rename from src/components/table/components/TableHead.tsx
rename to src/components/content-presentation/table/components/TableHead.tsx
index f3a08131..11f77bf4 100644
--- a/src/components/table/components/TableHead.tsx
+++ b/src/components/content-presentation/table/components/TableHead.tsx
@@ -1,12 +1,8 @@
-import React, { HTMLProps } from 'react';
+import React, { FC, HTMLProps } from 'react';
import classNames from 'classnames';
import TableSectionContext, { TableSection } from '../TableSectionContext';
-const TableHead: React.FC> = ({
- className,
- children,
- ...rest
-}) => (
+const TableHead: FC> = ({ className, children, ...rest }) => (
{children}
diff --git a/src/components/table/components/TablePanel.tsx b/src/components/content-presentation/table/components/TablePanel.tsx
similarity index 79%
rename from src/components/table/components/TablePanel.tsx
rename to src/components/content-presentation/table/components/TablePanel.tsx
index cd48ee4e..7b60290b 100644
--- a/src/components/table/components/TablePanel.tsx
+++ b/src/components/content-presentation/table/components/TablePanel.tsx
@@ -1,13 +1,13 @@
-import React, { ComponentProps, HTMLProps } from 'react';
+import React, { FC, ComponentProps, HTMLProps } from 'react';
import classNames from 'classnames';
-import HeadingLevel from '../../../util/HeadingLevel';
+import HeadingLevel from '@util/HeadingLevel';
export interface TablePanelProps extends HTMLProps {
heading?: string;
headingProps?: ComponentProps;
}
-const TablePanel: React.FC = ({
+const TablePanel: FC = ({
className,
heading,
headingProps,
diff --git a/src/components/table/components/TableRow.tsx b/src/components/content-presentation/table/components/TableRow.tsx
similarity index 78%
rename from src/components/table/components/TableRow.tsx
rename to src/components/content-presentation/table/components/TableRow.tsx
index 3a2039e3..05453cc7 100644
--- a/src/components/table/components/TableRow.tsx
+++ b/src/components/content-presentation/table/components/TableRow.tsx
@@ -1,10 +1,11 @@
+'use client';
import classNames from 'classnames';
-import React, { HTMLProps, useContext, useEffect } from 'react';
+import React, { Children, cloneElement, FC, HTMLProps, useContext, useEffect } from 'react';
import TableContext from '../TableContext';
import { getHeadingsFromChildren, isTableCell } from '../TableHelpers';
import TableSectionContext, { TableSection } from '../TableSectionContext';
-const TableRow: React.FC> = ({ className, children, ...rest }) => {
+const TableRow: FC> = ({ className, children, ...rest }) => {
const section = useContext(TableSectionContext);
const { isResponsive, headings, setHeadings } = useContext(TableContext);
@@ -15,9 +16,9 @@ const TableRow: React.FC> = ({ className, childre
}, [isResponsive, section, children]);
if (isResponsive && section === TableSection.BODY) {
- const tableCells = React.Children.map(children, (child, index) => {
+ const tableCells = Children.map(children, (child, index) => {
if (isTableCell(child)) {
- return React.cloneElement(child, {
+ return cloneElement(child, {
_responsive: isResponsive,
_responsiveHeading: `${headings[index] || ''} `,
});
diff --git a/src/components/table/components/__tests__/TableBody.test.tsx b/src/components/content-presentation/table/components/__tests__/TableBody.test.tsx
similarity index 79%
rename from src/components/table/components/__tests__/TableBody.test.tsx
rename to src/components/content-presentation/table/components/__tests__/TableBody.test.tsx
index 8452c819..ef92099b 100644
--- a/src/components/table/components/__tests__/TableBody.test.tsx
+++ b/src/components/content-presentation/table/components/__tests__/TableBody.test.tsx
@@ -1,14 +1,18 @@
-import { mount, shallow } from 'enzyme';
import React, { useContext } from 'react';
+import { render } from '@testing-library/react';
import Table from '../../Table';
import TableSectionContext, { TableSection } from '../../TableSectionContext';
import TableBody from '../TableBody';
describe('Table.Body', () => {
it('matches snapshot', () => {
- const wrapper = shallow( );
- expect(wrapper).toMatchSnapshot();
- wrapper.unmount();
+ const { container } = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
});
it('exposes TableSectionContext', () => {
@@ -24,7 +28,7 @@ describe('Table.Body', () => {
return null;
};
- const wrapper = mount(
+ render(
@@ -33,6 +37,5 @@ describe('Table.Body', () => {
);
expect(tableSection).toBe(TableSection.BODY);
- wrapper.unmount();
});
});
diff --git a/src/components/table/components/__tests__/TableCaption.test.tsx b/src/components/content-presentation/table/components/__tests__/TableCaption.test.tsx
similarity index 50%
rename from src/components/table/components/__tests__/TableCaption.test.tsx
rename to src/components/content-presentation/table/components/__tests__/TableCaption.test.tsx
index 2b01549e..f74f1ff5 100644
--- a/src/components/table/components/__tests__/TableCaption.test.tsx
+++ b/src/components/content-presentation/table/components/__tests__/TableCaption.test.tsx
@@ -1,11 +1,11 @@
-import { shallow } from 'enzyme';
import React from 'react';
+import { render } from '@testing-library/react';
import TableCaption from '../TableCaption';
describe('TableCaption', () => {
it('matches snapshot', () => {
- const wrapper = shallow( );
- expect(wrapper).toMatchSnapshot();
- wrapper.unmount();
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/src/components/table/components/__tests__/TableCell.test.tsx b/src/components/content-presentation/table/components/__tests__/TableCell.test.tsx
similarity index 59%
rename from src/components/table/components/__tests__/TableCell.test.tsx
rename to src/components/content-presentation/table/components/__tests__/TableCell.test.tsx
index c018d72d..b249ba5d 100644
--- a/src/components/table/components/__tests__/TableCell.test.tsx
+++ b/src/components/content-presentation/table/components/__tests__/TableCell.test.tsx
@@ -1,22 +1,22 @@
-import { mount, shallow } from 'enzyme';
import React from 'react';
+import { render } from '@testing-library/react';
+
import Table from '../../Table';
import TableBody from '../TableBody';
-
import TableCell from '../TableCell';
import TableHead from '../TableHead';
import TableRow from '../TableRow';
describe('Table.Cell', () => {
it('matches snapshot', () => {
- const wrapper = shallow( );
- expect(wrapper).toMatchSnapshot();
- wrapper.unmount();
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
});
it('prints dev warning when used outside of a head or body', () => {
jest.spyOn(console, 'warn').mockImplementation();
- const wrapper = mount(
+ render(
@@ -32,11 +32,10 @@ describe('Table.Cell', () => {
expect(console.warn).toHaveBeenLastCalledWith(
'Table.Cell used outside of a Table.Head or Table.Body component. Unable to determine section type from context.',
);
- wrapper.unmount();
});
it('returns th element when inside a Table.Head', () => {
- const wrapper = mount(
+ const { container } = render(
@@ -45,16 +44,13 @@ describe('Table.Cell', () => {
,
);
+ const cellWrapper = container.querySelector('th.nhsuk-table__header');
- const cellWrapper = wrapper.find('th');
-
- expect(cellWrapper.exists()).toBeTruthy();
- expect(cellWrapper.hasClass('nhsuk-table__header')).toBeTruthy();
- wrapper.unmount();
+ expect(cellWrapper).toBeTruthy();
});
it('returns td element when inside a Table.Body', () => {
- const wrapper = mount(
+ const { container } = render(
@@ -63,16 +59,13 @@ describe('Table.Cell', () => {
,
);
+ const cellWrapper = container.querySelector('td.nhsuk-table__cell');
- const cellWrapper = wrapper.find('td');
-
- expect(cellWrapper.exists()).toBeTruthy();
- expect(cellWrapper.hasClass('nhsuk-table__cell')).toBeTruthy();
- wrapper.unmount();
+ expect(cellWrapper).toBeTruthy();
});
it('adds responsive heading when _responsive=True', () => {
- const wrapper = mount(
+ const { container } = render(
@@ -86,20 +79,15 @@ describe('Table.Cell', () => {
,
);
+ const cellElement = container.querySelector('td');
+ const spanWrapper = container.querySelector('td > span');
- const cellWrapper = wrapper.find('td');
- expect(cellWrapper.exists()).toBeTruthy();
- expect(cellWrapper.prop('role')).toBe('cell');
-
- const spanWrapper = cellWrapper.find('span');
- expect(spanWrapper.exists()).toBeTruthy();
- expect(spanWrapper.text()).toBe('TestHeading ');
-
- wrapper.unmount();
+ expect(cellElement).toBeTruthy();
+ expect(spanWrapper?.textContent).toBe('TestHeading ');
});
it('adds the numeric class when isNumeric is true', () => {
- const wrapper = mount(
+ const { container } = render(
@@ -108,15 +96,13 @@ describe('Table.Cell', () => {
,
);
+ const cell = container.querySelector('td[data-test="cell"].nhsuk-table__cell--numeric');
- const cell = wrapper.find('[data-test="cell"]');
- expect(cell.last().prop('className')).toContain('nhsuk-table__cell--numeric');
-
- wrapper.unmount();
+ expect(cell).toBeTruthy();
});
it('adds the numeric header class when isNumeric is true', () => {
- const wrapper = mount(
+ const { container } = render(
@@ -125,15 +111,13 @@ describe('Table.Cell', () => {
,
);
+ const cell = container.querySelector('th[data-test="cell"].nhsuk-table__header--numeric');
- const cell = wrapper.find('[data-test="cell"]');
- expect(cell.last().prop('className')).toContain('nhsuk-table__header--numeric');
-
- wrapper.unmount();
+ expect(cell).toBeTruthy();
});
it('does not add the numeric header when isNumeric is false', () => {
- const wrapper = mount(
+ const { container } = render(
@@ -147,12 +131,10 @@ describe('Table.Cell', () => {
,
);
+ const header = container.querySelector('th[data-test="header"].nhsuk-table__header--numeric');
+ const cell = container.querySelector('td[data-test="cell"].nhsuk-table__header--numeric');
- const header = wrapper.find('[data-test="header"]');
- expect(header.last().prop('className')).not.toContain('nhsuk-table__header--numeric');
- const cell = wrapper.find('[data-test="cell"]');
- expect(cell.last().prop('className')).not.toContain('nhsuk-table__header--numeric');
-
- wrapper.unmount();
+ expect(header).toBeFalsy();
+ expect(cell).toBeFalsy();
});
});
diff --git a/src/components/table/components/__tests__/TableContainer.test.tsx b/src/components/content-presentation/table/components/__tests__/TableContainer.test.tsx
similarity index 51%
rename from src/components/table/components/__tests__/TableContainer.test.tsx
rename to src/components/content-presentation/table/components/__tests__/TableContainer.test.tsx
index 049ac52d..6a2ad68d 100644
--- a/src/components/table/components/__tests__/TableContainer.test.tsx
+++ b/src/components/content-presentation/table/components/__tests__/TableContainer.test.tsx
@@ -1,11 +1,11 @@
-import { shallow } from 'enzyme';
import React from 'react';
+import { render } from '@testing-library/react';
import TableContainer from '../TableContainer';
describe('TableContainer', () => {
it('matches snapshot', () => {
- const wrapper = shallow( );
- expect(wrapper).toMatchSnapshot();
- wrapper.unmount();
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
});
});
diff --git a/src/components/table/components/__tests__/TableHead.test.tsx b/src/components/content-presentation/table/components/__tests__/TableHead.test.tsx
similarity index 79%
rename from src/components/table/components/__tests__/TableHead.test.tsx
rename to src/components/content-presentation/table/components/__tests__/TableHead.test.tsx
index 98744b01..616fa724 100644
--- a/src/components/table/components/__tests__/TableHead.test.tsx
+++ b/src/components/content-presentation/table/components/__tests__/TableHead.test.tsx
@@ -1,14 +1,14 @@
-import { mount, shallow } from 'enzyme';
import React, { useContext } from 'react';
+import { render } from '@testing-library/react';
import Table from '../../Table';
import TableSectionContext, { TableSection } from '../../TableSectionContext';
import TableHead from '../TableHead';
describe('Table.Head', () => {
it('matches snapshot', () => {
- const wrapper = shallow( );
- expect(wrapper).toMatchSnapshot();
- wrapper.unmount();
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
});
it('exposes TableSectionContext', () => {
@@ -24,7 +24,7 @@ describe('Table.Head', () => {
return null;
};
- const wrapper = mount(
+ render(
@@ -33,6 +33,5 @@ describe('Table.Head', () => {
);
expect(tableSection).toBe(TableSection.HEAD);
- wrapper.unmount();
});
});
diff --git a/src/components/content-presentation/table/components/__tests__/TablePanel.test.tsx b/src/components/content-presentation/table/components/__tests__/TablePanel.test.tsx
new file mode 100644
index 00000000..b31c65d8
--- /dev/null
+++ b/src/components/content-presentation/table/components/__tests__/TablePanel.test.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import TablePanel from '../TablePanel';
+
+describe('Table.Panel', () => {
+ it('matches snapshot', () => {
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('matches snapshot when rendering a h2 heading', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('adds header when prop added', () => {
+ const { container } = render(
+ ,
+ );
+ const heading = container.querySelector('h2.nhsuk-table__heading-tab');
+
+ expect(heading).toBeTruthy();
+ });
+});
diff --git a/src/components/table/components/__tests__/TableRow.test.tsx b/src/components/content-presentation/table/components/__tests__/TableRow.test.tsx
similarity index 76%
rename from src/components/table/components/__tests__/TableRow.test.tsx
rename to src/components/content-presentation/table/components/__tests__/TableRow.test.tsx
index 03241b29..5c16e634 100644
--- a/src/components/table/components/__tests__/TableRow.test.tsx
+++ b/src/components/content-presentation/table/components/__tests__/TableRow.test.tsx
@@ -1,25 +1,25 @@
-import { mount, ReactWrapper } from 'enzyme';
+import { render } from '@testing-library/react';
import React from 'react';
import TableContext, { ITableContext } from '../../TableContext';
import TableSectionContext, { TableSection } from '../../TableSectionContext';
import TableCell from '../TableCell';
import TableRow from '../TableRow';
-const assertCellText = (c: ReactWrapper, cellNumber: number, text: string) => {
- expect(c.find(`[data-test="cell-${cellNumber}"]`).first().text()).toEqual(text);
-}
+const assertCellText = (container: HTMLElement, cellNumber: number, text: string) => {
+ expect(container.querySelector(`[data-test="cell-${cellNumber}"]`)?.textContent).toEqual(text);
+};
describe('Table.Row', () => {
it('matches snapshot', () => {
- const wrapper = mount(
+ const { container } = render(
,
);
- expect(wrapper).toMatchSnapshot();
- wrapper.unmount();
+
+ expect(container).toMatchSnapshot();
});
it('renders headers in the first column if responsive', () => {
@@ -28,7 +28,7 @@ describe('Table.Row', () => {
headings: ['a', 'b', 'c'],
setHeadings: jest.fn(),
};
- const wrapper = mount(
+ const { container } = render(
@@ -41,15 +41,13 @@ describe('Table.Row', () => {
-
+
,
);
-
- assertCellText(wrapper, 1, 'a 1');
- assertCellText(wrapper, 2, 'b 2');
- assertCellText(wrapper, 3, 'c 3');
- expect(wrapper.find('.nhsuk-table-responsive__heading').length).toBe(3);
-
- wrapper.unmount();
+
+ assertCellText(container, 1, 'a 1');
+ assertCellText(container, 2, 'b 2');
+ assertCellText(container, 3, 'c 3');
+ expect(container.querySelectorAll('.nhsuk-table-responsive__heading').length).toBe(3);
});
it('renders row contents without headers in responsive mode if they are not cells', () => {
@@ -58,7 +56,7 @@ describe('Table.Row', () => {
headings: ['a', 'b', 'c'],
setHeadings: jest.fn(),
};
- const wrapper = mount(
+ const { container } = render(
@@ -71,15 +69,13 @@ describe('Table.Row', () => {
-
+
,
);
-
- assertCellText(wrapper, 1, '1');
- assertCellText(wrapper, 2, '2');
- assertCellText(wrapper, 3, '3');
- expect(wrapper.find('.nhsuk-table-responsive__heading').length).toBe(0);
-
- wrapper.unmount();
+
+ assertCellText(container, 1, '1');
+ assertCellText(container, 2, '2');
+ assertCellText(container, 3, '3');
+ expect(container.querySelectorAll('.nhsuk-table-responsive__heading').length).toBe(0);
});
it('renders row contents as headers in head section in responsive mode', () => {
@@ -89,7 +85,7 @@ describe('Table.Row', () => {
headings: ['a', 'b', 'c'],
setHeadings,
};
- const wrapper = mount(
+ render(
@@ -102,12 +98,10 @@ describe('Table.Row', () => {
-
+
,
);
-
- expect(setHeadings).toHaveBeenCalledWith(['1', '2', '3']);
- wrapper.unmount();
+ expect(setHeadings).toHaveBeenCalledWith(['1', '2', '3']);
});
it('sets headers, skipping contents outside of table cells in responsive mode', () => {
@@ -117,7 +111,7 @@ describe('Table.Row', () => {
headings: ['a', 'b', 'c'],
setHeadings,
};
- const wrapper = mount(
+ render(
@@ -130,12 +124,10 @@ describe('Table.Row', () => {
-
+ ,
);
-
- expect(setHeadings).toHaveBeenCalledWith(['2']);
- wrapper.unmount();
+ expect(setHeadings).toHaveBeenCalledWith(['2']);
});
it('does not render row contents as headers in head section in normal mode', () => {
@@ -144,7 +136,7 @@ describe('Table.Row', () => {
headings: ['a', 'b', 'c'],
setHeadings: jest.fn(),
};
- const wrapper = mount(
+ const { container } = render(
@@ -157,14 +149,12 @@ describe('Table.Row', () => {
-
+ ,
);
-
- assertCellText(wrapper, 1, '1');
- assertCellText(wrapper, 2, '2');
- assertCellText(wrapper, 3, '3');
- expect(wrapper.find('.nhsuk-table-responsive__heading').length).toBe(0);
-
- wrapper.unmount();
+
+ assertCellText(container, 1, '1');
+ assertCellText(container, 2, '2');
+ assertCellText(container, 3, '3');
+ expect(container.querySelectorAll('.nhsuk-table-responsive__heading').length).toBe(0);
});
});
diff --git a/src/components/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap
similarity index 51%
rename from src/components/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap
rename to src/components/content-presentation/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap
index 38158193..4ecef708 100644
--- a/src/components/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap
+++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap
@@ -1,11 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table.Body matches snapshot 1`] = `
-
-
-
+
`;
diff --git a/src/components/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap
similarity index 60%
rename from src/components/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap
rename to src/components/content-presentation/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap
index 873fb4d8..35ac72d9 100644
--- a/src/components/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap
+++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TableCaption matches snapshot 1`] = `
-
+
+
+
`;
diff --git a/src/components/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap
similarity index 63%
rename from src/components/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap
rename to src/components/content-presentation/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap
index 3ccfe28e..0d21a21f 100644
--- a/src/components/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap
+++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table.Cell matches snapshot 1`] = `
-
+
+
+
`;
diff --git a/src/components/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap
similarity index 61%
rename from src/components/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap
rename to src/components/content-presentation/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap
index 1f94f03f..194e45d3 100644
--- a/src/components/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap
+++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TableContainer matches snapshot 1`] = `
-
+
`;
diff --git a/src/components/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap
similarity index 53%
rename from src/components/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap
rename to src/components/content-presentation/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap
index e703f809..97024eca 100644
--- a/src/components/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap
+++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap
@@ -1,11 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table.Head matches snapshot 1`] = `
-
-
+
-
+
`;
diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TablePanel.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TablePanel.test.tsx.snap
new file mode 100644
index 00000000..60177755
--- /dev/null
+++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TablePanel.test.tsx.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Table.Panel matches snapshot 1`] = `
+
+`;
+
+exports[`Table.Panel matches snapshot when rendering a h2 heading 1`] = `
+
+`;
diff --git a/src/components/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap
similarity index 50%
rename from src/components/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap
rename to src/components/content-presentation/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap
index f1e3be1a..7c15e8b4 100644
--- a/src/components/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap
+++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap
@@ -1,13 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table.Row matches snapshot 1`] = `
-
+
`;
diff --git a/src/components/table/index.ts b/src/components/content-presentation/table/index.ts
similarity index 100%
rename from src/components/table/index.ts
rename to src/components/content-presentation/table/index.ts
diff --git a/src/components/content-presentation/tabs/Tabs.tsx b/src/components/content-presentation/tabs/Tabs.tsx
new file mode 100644
index 00000000..db921a0c
--- /dev/null
+++ b/src/components/content-presentation/tabs/Tabs.tsx
@@ -0,0 +1,73 @@
+'use client';
+import classNames from 'classnames';
+import React, { FC, HTMLAttributes, useEffect } from 'react';
+import HeadingLevel, { HeadingLevelType } from '@util/HeadingLevel';
+import TabsJs from '@resources/tabs';
+
+type TabsProps = HTMLAttributes;
+
+type TabTitleProps = { children: React.ReactNode; headingLevel?: HeadingLevelType };
+
+type TabListProps = {
+ children: React.ReactNode;
+};
+
+type TabListItemProps = {
+ id: string;
+ children: React.ReactNode;
+};
+
+type TabContentsProps = {
+ id: string;
+ children: React.ReactNode;
+};
+
+const TabTitle: FC = ({ children, headingLevel = 'h2' }) => (
+
+ {children}
+
+);
+
+const TabList: FC = ({ children }) => (
+
+);
+
+const TabListItem: FC = ({ id, children }) => (
+
+
+ {children}
+
+
+);
+
+const TabContents: FC = ({ id, children }) => (
+
+ {children}
+
+);
+
+interface Tabs extends FC {
+ Title: FC;
+ List: FC;
+ ListItem: FC;
+ Contents: FC;
+}
+
+const Tabs: Tabs = ({ className, children, ...rest }) => {
+ useEffect(() => {
+ TabsJs();
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+Tabs.Title = TabTitle;
+Tabs.List = TabList;
+Tabs.ListItem = TabListItem;
+Tabs.Contents = TabContents;
+
+export default Tabs;
diff --git a/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx b/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx
new file mode 100644
index 00000000..33e5ee9c
--- /dev/null
+++ b/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx
@@ -0,0 +1,146 @@
+import React from 'react';
+import { fireEvent, render } from '@testing-library/react';
+import Tabs from '../Tabs';
+import { HeadingLevelType } from '@util/HeadingLevel';
+
+describe('The tabs component', () => {
+ it('Matches the snapshot', () => {
+ const { container } = render(
+
+ Contents
+
+ Past day
+ Past week
+ Past month
+
+
+
+ Past day contents go here
+
+
+
+ Past week contents go here
+
+
+
+ Past month contents go here
+
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('Switches the visibility of tabs when clicked', () => {
+ const { container } = render(
+
+ Contents
+
+ Tab One
+ Tab Two
+
+
+
+ Tab one contents go here
+
+
+
+ Tab two contents go here
+
+ ,
+ );
+
+ const firstTabLink = container.querySelector('#tab_tab-one');
+ const secondTabLink = container.querySelector('#tab_tab-two');
+
+ expect(
+ firstTabLink?.parentElement?.classList.contains('nhsuk-tabs__list-item--selected'),
+ ).toEqual(true);
+ expect(
+ secondTabLink?.parentElement?.classList.contains('nhsuk-tabs__list-item--selected'),
+ ).toEqual(false);
+
+ fireEvent.click(secondTabLink!);
+
+ expect(
+ firstTabLink?.parentElement?.classList.contains('nhsuk-tabs__list-item--selected'),
+ ).toEqual(false);
+ expect(
+ secondTabLink?.parentElement?.classList.contains('nhsuk-tabs__list-item--selected'),
+ ).toEqual(true);
+ });
+
+ describe('The tabs title', () => {
+ it.each`
+ headingLevel
+ ${undefined}
+ ${'H1'}
+ ${'H2'}
+ ${'H3'}
+ ${'H4'}
+ `(
+ 'Renders the chosen heading level $headingLevel if specified',
+ ({ headingLevel }: { headingLevel: HeadingLevelType }) => {
+ const { container } = render(
+ Test title ,
+ );
+
+ const title = container.querySelector('.nhsuk-tabs__title');
+
+ expect(title?.nodeName).toEqual(headingLevel ?? 'H2');
+ },
+ );
+ });
+
+ describe('The tab list', () => {
+ it('Renders the expected children', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ const listElement = container.querySelector('.nhsuk-tabs__list');
+
+ expect(listElement?.querySelector('#list-contents')).toBeTruthy();
+ });
+ });
+
+ describe('The tab list item', () => {
+ it('Sets the href to be the passed in id prop', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-tabs__tab')?.getAttribute('href')).toBe('#test-id');
+ });
+
+ it('Renders the expected children', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ const tabElement = container.querySelector('.nhsuk-tabs__tab');
+
+ expect(tabElement?.querySelector('#list-item-contents')).toBeTruthy();
+ });
+ });
+
+ describe('The tab contents', () => {
+ it('Renders the expected children', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ const tabElement = container.querySelector('#test-contents');
+
+ expect(tabElement?.querySelector('#tab-contents')).toBeTruthy();
+ });
+ });
+});
diff --git a/src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap b/src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap
new file mode 100644
index 00000000..01685569
--- /dev/null
+++ b/src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap
@@ -0,0 +1,99 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`The tabs component Matches the snapshot 1`] = `
+
+
+
+ Contents
+
+
+
+
+ Past day contents go here
+
+
+
+
+ Past week contents go here
+
+
+
+
+ Past month contents go here
+
+
+
+
+`;
diff --git a/src/components/content-presentation/tabs/index.ts b/src/components/content-presentation/tabs/index.ts
new file mode 100644
index 00000000..4b674c98
--- /dev/null
+++ b/src/components/content-presentation/tabs/index.ts
@@ -0,0 +1,3 @@
+import Tabs from './Tabs';
+
+export default Tabs;
diff --git a/src/components/content-presentation/tag/Tag.tsx b/src/components/content-presentation/tag/Tag.tsx
new file mode 100644
index 00000000..3774ec99
--- /dev/null
+++ b/src/components/content-presentation/tag/Tag.tsx
@@ -0,0 +1,25 @@
+import React, { FC, HTMLProps } from 'react';
+import classNames from 'classnames';
+
+interface TagProps extends HTMLProps {
+ color?:
+ | 'white'
+ | 'grey'
+ | 'green'
+ | 'aqua-green'
+ | 'blue'
+ | 'purple'
+ | 'pink'
+ | 'red'
+ | 'orange'
+ | 'yellow';
+}
+
+const Tag: FC = ({ className, color, ...rest }) => (
+
+);
+
+export default Tag;
diff --git a/src/components/content-presentation/tag/__tests__/Tag.test.tsx b/src/components/content-presentation/tag/__tests__/Tag.test.tsx
new file mode 100644
index 00000000..87202886
--- /dev/null
+++ b/src/components/content-presentation/tag/__tests__/Tag.test.tsx
@@ -0,0 +1,34 @@
+import React, { ComponentProps } from 'react';
+import { render } from '@testing-library/react';
+import Tag from '../Tag';
+
+describe('Tag', () => {
+ it('matches snapshot', () => {
+ const { container } = render(Active );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('renders a nhsuk-tag class', () => {
+ const { container } = render(Active );
+
+ expect(container.querySelector('strong.nhsuk-tag')).toBeTruthy();
+ });
+
+ it.each['color']>([
+ 'white',
+ 'grey',
+ 'green',
+ 'aqua-green',
+ 'blue',
+ 'purple',
+ 'pink',
+ 'red',
+ 'orange',
+ 'yellow',
+ ])('adds colour class %s ', (colour) => {
+ const { container } = render( );
+
+ expect(container.querySelector(`strong.nhsuk-tag.nhsuk-tag--${colour}`)).toBeTruthy();
+ });
+});
diff --git a/src/components/tag/__tests__/__snapshots__/Tag.test.tsx.snap b/src/components/content-presentation/tag/__tests__/__snapshots__/Tag.test.tsx.snap
similarity index 54%
rename from src/components/tag/__tests__/__snapshots__/Tag.test.tsx.snap
rename to src/components/content-presentation/tag/__tests__/__snapshots__/Tag.test.tsx.snap
index 7446deff..d0ec444e 100644
--- a/src/components/tag/__tests__/__snapshots__/Tag.test.tsx.snap
+++ b/src/components/content-presentation/tag/__tests__/__snapshots__/Tag.test.tsx.snap
@@ -1,9 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Tag matches snapshot 1`] = `
-
- Active
-
+
+
+ Active
+
+
`;
diff --git a/src/components/tag/index.ts b/src/components/content-presentation/tag/index.ts
similarity index 100%
rename from src/components/tag/index.ts
rename to src/components/content-presentation/tag/index.ts
diff --git a/src/components/warning-callout/WarningCallout.tsx b/src/components/content-presentation/warning-callout/WarningCallout.tsx
similarity index 69%
rename from src/components/warning-callout/WarningCallout.tsx
rename to src/components/content-presentation/warning-callout/WarningCallout.tsx
index 99d7245c..d63f5283 100644
--- a/src/components/warning-callout/WarningCallout.tsx
+++ b/src/components/content-presentation/warning-callout/WarningCallout.tsx
@@ -1,15 +1,15 @@
-import React, { HTMLProps } from 'react';
+import React, { FC, HTMLProps } from 'react';
import classNames from 'classnames';
-import HeadingLevel, { HeadingLevelType } from '../../util/HeadingLevel';
+import HeadingLevel, { HeadingLevelType } from '@util/HeadingLevel';
interface WarningCalloutLabelProps extends HTMLProps {
headingLevel?: HeadingLevelType;
visuallyHiddenText?: string | false;
}
-const WarningCalloutLabel: React.FC = ({
+const WarningCalloutLabel: FC = ({
className,
- visuallyHiddenText,
+ visuallyHiddenText = 'Important: ',
children,
...rest
}) => (
@@ -22,11 +22,7 @@ const WarningCalloutLabel: React.FC = ({
);
-WarningCalloutLabel.defaultProps = {
- visuallyHiddenText: 'Important: ',
-};
-
-interface IWarningCallout extends React.FC> {
+interface IWarningCallout extends FC> {
Label: typeof WarningCalloutLabel;
}
diff --git a/src/components/warning-callout/__tests__/WarningCallout.test.tsx b/src/components/content-presentation/warning-callout/__tests__/WarningCallout.test.tsx
similarity index 74%
rename from src/components/warning-callout/__tests__/WarningCallout.test.tsx
rename to src/components/content-presentation/warning-callout/__tests__/WarningCallout.test.tsx
index 8d07953b..90ea5950 100644
--- a/src/components/warning-callout/__tests__/WarningCallout.test.tsx
+++ b/src/components/content-presentation/warning-callout/__tests__/WarningCallout.test.tsx
@@ -1,10 +1,10 @@
import React from 'react';
-import { mount } from 'enzyme';
+import { render } from '@testing-library/react';
import WarningCallout from '../WarningCallout';
describe('WarningCallout', () => {
it('matches snapshot', () => {
- const wrapper = mount(
+ const { container } = render(
School, nursery or work
@@ -14,12 +14,11 @@ describe('WarningCallout', () => {
,
);
- expect(wrapper).toMatchSnapshot();
- wrapper.unmount();
+ expect(container).toMatchSnapshot();
});
it('adds default visually hidden text', () => {
- const wrapper = mount(
+ const { container } = render(
School, nursery or work
@@ -29,13 +28,13 @@ describe('WarningCallout', () => {
,
);
- expect(wrapper.find(WarningCallout.Label).text()).toBe('Important: School, nursery or work');
-
- wrapper.unmount();
+ expect(container.querySelector('.nhsuk-warning-callout__label')?.textContent).toBe(
+ 'Important: School, nursery or work',
+ );
});
it('adds custom visually hidden text', () => {
- const wrapper = mount(
+ const { container } = render(
School, nursery or work
@@ -47,15 +46,13 @@ describe('WarningCallout', () => {
,
);
- expect(wrapper.find(WarningCallout.Label).text()).toBe(
+ expect(container.querySelector('.nhsuk-warning-callout__label')?.textContent).toBe(
'Not Very Important: School, nursery or work',
);
-
- wrapper.unmount();
});
it('can disable visually hidden text', () => {
- const wrapper = mount(
+ const { container } = render(
School, nursery or work
@@ -67,8 +64,8 @@ describe('WarningCallout', () => {
,
);
- expect(wrapper.find(WarningCallout.Label).text()).toBe('School, nursery or work');
-
- wrapper.unmount();
+ expect(container.querySelector('.nhsuk-warning-callout__label')?.textContent).toBe(
+ 'School, nursery or work',
+ );
});
});
diff --git a/src/components/content-presentation/warning-callout/__tests__/__snapshots__/WarningCallout.test.tsx.snap b/src/components/content-presentation/warning-callout/__tests__/__snapshots__/WarningCallout.test.tsx.snap
new file mode 100644
index 00000000..5678f501
--- /dev/null
+++ b/src/components/content-presentation/warning-callout/__tests__/__snapshots__/WarningCallout.test.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`WarningCallout matches snapshot 1`] = `
+
+
+
+
+
+ Important:
+
+ School, nursery or work
+
+
+
+ Stay away from school, nursery or work until all the spots have crusted over. This is usually 5 days after the spots first appeared.
+
+
+
+`;
diff --git a/src/components/warning-callout/index.ts b/src/components/content-presentation/warning-callout/index.ts
similarity index 100%
rename from src/components/warning-callout/index.ts
rename to src/components/content-presentation/warning-callout/index.ts
diff --git a/src/components/contents-list/__tests__/ContentsList.test.tsx b/src/components/contents-list/__tests__/ContentsList.test.tsx
deleted file mode 100644
index b80d0ea5..00000000
--- a/src/components/contents-list/__tests__/ContentsList.test.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import ContentsList from '..';
-
-describe('ContentsList', () => {
- it('matches snapshot', () => {
- const element = shallow( );
- expect(element).toMatchSnapshot('ContentsList');
- element.unmount();
- });
-
- it('renders default hidden text', () => {
- const element = shallow( );
- expect(element.find('.nhsuk-u-visually-hidden').text()).toEqual('Contents');
- element.unmount();
- });
-
- it('renders custom hidden text', () => {
- const element = shallow( );
- expect(element.find('.nhsuk-u-visually-hidden').text()).toEqual('Custom');
- element.unmount();
- });
-
- it('disables hidden text', () => {
- const element = shallow( );
- expect(element.find('.nhsuk-u-visually-hidden').exists()).toBeFalsy();
- element.unmount();
- });
-
- describe('ContentsList.Item', () => {
- it('matches snapshot', () => {
- const element = shallow(Content );
- expect(element).toMatchSnapshot('ContentsList.Item');
- element.unmount();
- });
-
- it('renders as span when current', () => {
- const element = shallow(Content );
- expect(
- element.containsMatchingElement(
- Content ,
- ),
- ).toBeTruthy();
- element.unmount();
- });
-
- it('normally renders as anchor', () => {
- const element = shallow(Content );
- expect(
- // eslint-disable-next-line jsx-a11y/anchor-is-valid
- element.containsMatchingElement(Content ),
- ).toBeTruthy();
- element.unmount();
- });
- });
-});
diff --git a/src/components/contents-list/__tests__/__snapshots__/ContentsList.test.tsx.snap b/src/components/contents-list/__tests__/__snapshots__/ContentsList.test.tsx.snap
deleted file mode 100644
index d132243d..00000000
--- a/src/components/contents-list/__tests__/__snapshots__/ContentsList.test.tsx.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ContentsList ContentsList.Item matches snapshot: ContentsList.Item 1`] = `
-
-
- Content
-
-
-`;
-
-exports[`ContentsList matches snapshot: ContentsList 1`] = `
-
-
- Contents
-
-
-
-`;
diff --git a/src/components/date-input/DateInput.tsx b/src/components/date-input/DateInput.tsx
deleted file mode 100644
index 1c98b933..00000000
--- a/src/components/date-input/DateInput.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import React, { HTMLProps, PureComponent, ChangeEvent } from 'react';
-import classNames from 'classnames';
-import { DayInput, MonthInput, YearInput } from './components/IndividualDateInputs';
-import FormGroup from '../../util/FormGroup';
-import DateInputContext, { IDateInputContext } from './DateInputContext';
-import { FormElementProps } from '../../util/types/FormTypes';
-
-type DateInputValue = {
- day: string;
- month: string;
- year: string;
-};
-
-type DateInputChangeEvent = ChangeEvent & {
- target: HTMLInputElement & { value: DateInputValue };
- currentTarget: HTMLInputElement & { value: DateInputValue };
-};
-
-interface DateInputProps
- extends Omit, 'value' | 'defaultValue'>,
- FormElementProps {
- autoSelectNext?: boolean;
- value?: Partial;
- defaultValue?: Partial;
- onChange?: (e: DateInputChangeEvent) => void;
-}
-
-interface DateInputState {
- values: {
- day: string;
- month: string;
- year: string;
- };
-}
-
-interface DateInput extends PureComponent {
- monthRef: null | HTMLInputElement;
- yearRef: null | HTMLInputElement;
-}
-
-class DateInput extends PureComponent {
- static Day = DayInput;
-
- static Month = MonthInput;
-
- static Year = YearInput;
-
- constructor(props: DateInputProps) {
- super(props);
- this.state = {
- values: {
- day: props.value?.day || '',
- month: props.value?.month || '',
- year: props.value?.year || '',
- },
- };
-
- this.monthRef = null;
- this.yearRef = null;
- }
-
- componentDidUpdate(prevProps: DateInputProps): void {
- if (this.props.value && prevProps.value !== this.props.value) {
- // This is the only way that we can update our internal state
- // when the value updates. We check if the value has changed first,
- // preventing an infinite loop.
- //
- // eslint-disable-next-line react/no-did-update-set-state
- this.setState((state) => {
- if (!this.props.value) return state;
-
- const newState = { ...state };
- const { day, month, year } = this.props.value;
- if (day && day !== state.values.day) newState.values.day = day;
- if (month && month !== state.values.month) newState.values.month = month;
- if (year && year !== state.values.year) newState.values.year = year;
-
- return newState;
- });
- }
- }
-
- handleSelectNext = (inputType: 'day' | 'month' | 'year', value: string): void => {
- if (!this.props.autoSelectNext) return;
- if (inputType === 'day' && value.length === 2 && this.monthRef) {
- this.monthRef.focus();
- } else if (inputType === 'month' && value.length === 2 && this.yearRef) {
- this.yearRef.focus();
- }
- };
-
- handleChange = (
- inputType: 'day' | 'month' | 'year',
- event: ChangeEvent,
- ): void => {
- this.handleSelectNext(inputType, event.target.value);
- event.stopPropagation();
- this.setState((state) => {
- const newEventValue = {
- ...state.values,
- [inputType]: event.target.value,
- };
- if (this.props.onChange) {
- const newEvent = {
- ...event,
- target: { ...event.target, value: newEventValue },
- currentTarget: { ...event.currentTarget, value: newEventValue },
- } as DateInputChangeEvent;
- this.props.onChange(newEvent);
- }
- return { values: newEventValue };
- });
- };
-
- registerRef = (inputType: 'day' | 'month' | 'year', ref: HTMLInputElement | null): void => {
- if (inputType === 'month') this.monthRef = ref;
- if (inputType === 'year') this.yearRef = ref;
- };
-
- render(): JSX.Element {
- const {
- children,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- onChange,
- value,
- defaultValue,
- ...rest
- } = this.props;
-
- return (
- > inputType="dateinput" {...rest}>
- {/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
- {({ className, name, id, error, autoSelectNext, ...restRenderProps }) => {
- const contextValue: IDateInputContext = {
- id,
- name,
- error,
- value,
- defaultValue,
- handleChange: this.handleChange,
- registerRef: this.registerRef,
- };
- return (
-
-
- {children || (
- <>
-
-
-
- >
- )}
-
-
- );
- }}
-
- );
- }
-}
-
-export default DateInput;
diff --git a/src/components/date-input/__tests__/DateInput.test.tsx b/src/components/date-input/__tests__/DateInput.test.tsx
deleted file mode 100644
index a872787b..00000000
--- a/src/components/date-input/__tests__/DateInput.test.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import DateInput from '../DateInput';
-
-describe('DateInput', () => {
- it('matches snapshot', () => {
- const component = mount( );
- expect(component).toMatchSnapshot();
- component.unmount();
- });
-
- it('wraps onChange handlers', () => {
- const onChange = jest.fn();
- const component = mount( );
- // Day
- component
- .find('#testInput-day')
- .simulate('change', { target: { name: 'testInput-day', value: '27' } });
- expect(component.state('values')).toEqual({
- day: '27',
- month: '',
- year: '',
- });
- expect(onChange).toHaveBeenCalledTimes(1);
- expect(onChange.mock.calls[0][0].target.value).toEqual({
- day: '27',
- month: '',
- year: '',
- });
-
- // Month
- component
- .find('#testInput-month')
- .simulate('change', { target: { name: 'testInput-month', value: '06' } });
- expect(component.state('values')).toEqual({
- day: '27',
- month: '06',
- year: '',
- });
- expect(onChange).toHaveBeenCalledTimes(2);
- expect(onChange.mock.calls[1][0].target.value).toEqual({
- day: '27',
- month: '06',
- year: '',
- });
-
- // Year
- component
- .find('#testInput-year')
- .simulate('change', { target: { name: 'testInput-year', value: '2000' } });
- expect(component.state('values')).toEqual({
- day: '27',
- month: '06',
- year: '2000',
- });
- expect(onChange).toHaveBeenCalledTimes(3);
- expect(onChange.mock.calls[2][0].target.value).toEqual({
- day: '27',
- month: '06',
- year: '2000',
- });
-
- component.unmount();
- });
-});
diff --git a/src/components/date-input/__tests__/__snapshots__/DateInput.test.tsx.snap b/src/components/date-input/__tests__/__snapshots__/DateInput.test.tsx.snap
deleted file mode 100644
index b8aa1b1d..00000000
--- a/src/components/date-input/__tests__/__snapshots__/DateInput.test.tsx.snap
+++ /dev/null
@@ -1,162 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`DateInput matches snapshot 1`] = `
-
-
-
-
-
-`;
diff --git a/src/components/details/__tests__/Details.test.tsx b/src/components/details/__tests__/Details.test.tsx
deleted file mode 100644
index e84b36e6..00000000
--- a/src/components/details/__tests__/Details.test.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import Details from '..';
-
-describe('Details', () => {
- it('matches snapshot', () => {
- const standardDetails = shallow( );
- const expanderDetails = shallow( );
- expect(standardDetails).toMatchSnapshot('StandardDetails');
- expect(expanderDetails).toMatchSnapshot('ExpanderDetails');
- standardDetails.unmount();
- expanderDetails.unmount();
- });
-
- it('adds expander classes', () => {
- const expander = shallow( );
- expect(expander.hasClass('nhsuk-expander')).toBeTruthy();
- expander.unmount();
- });
-
- describe('Details.Summary', () => {
- it('matches snapshot', () => {
- const element = shallow(Content );
- expect(element).toMatchSnapshot('Details.Summary');
- expect(element.type()).toEqual('summary');
- expect(
- element.containsMatchingElement(
- Content ,
- ),
- ).toBeTruthy();
- element.unmount();
- });
- });
-
- describe('Details.Text', () => {
- it('matches snapshot', () => {
- const element = shallow(Text );
- expect(element).toMatchSnapshot('Details.Text');
- expect(element.type()).toEqual('div');
- element.unmount();
- });
- });
-
- describe('Details.ExpanderGroup', () => {
- it('matches snapshot', () => {
- const element = shallow( );
- expect(element).toMatchSnapshot('Details.ExpanderGroup');
- expect(element.type()).toEqual('div');
- element.unmount();
- });
- });
-});
diff --git a/src/components/details/__tests__/__snapshots__/Details.test.tsx.snap b/src/components/details/__tests__/__snapshots__/Details.test.tsx.snap
deleted file mode 100644
index b5bdf688..00000000
--- a/src/components/details/__tests__/__snapshots__/Details.test.tsx.snap
+++ /dev/null
@@ -1,39 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Details Details.ExpanderGroup matches snapshot: Details.ExpanderGroup 1`] = `
-
-`;
-
-exports[`Details Details.Summary matches snapshot: Details.Summary 1`] = `
-
-
- Content
-
-
-`;
-
-exports[`Details Details.Text matches snapshot: Details.Text 1`] = `
-
- Text
-
-`;
-
-exports[`Details matches snapshot: ExpanderDetails 1`] = `
-
-`;
-
-exports[`Details matches snapshot: StandardDetails 1`] = `
-
-`;
diff --git a/src/components/do-dont-list/__tests__/DoDontList.test.tsx b/src/components/do-dont-list/__tests__/DoDontList.test.tsx
deleted file mode 100644
index 461174b0..00000000
--- a/src/components/do-dont-list/__tests__/DoDontList.test.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import React from 'react';
-import { shallow, mount } from 'enzyme';
-import DoDontList from '..';
-
-describe('DoDontList', () => {
- it('matches snapshot', () => {
- const doElement = shallow( );
- const dontElement = shallow( );
- expect(doElement).toMatchSnapshot('DoDontList-Do');
- expect(dontElement).toMatchSnapshot('DoDontList-Dont');
- doElement.unmount();
- dontElement.unmount();
- });
-
- it('adds the correct header', () => {
- const doElement = mount( );
- const dontElement = mount( );
- expect(doElement.find('h3.nhsuk-do-dont-list__label').text()).toEqual('Do');
- expect(dontElement.find('h3.nhsuk-do-dont-list__label').text()).toEqual("Don't");
- doElement.unmount();
- dontElement.unmount();
- });
-
- it('adds the correct classes', () => {
- const doElement = mount( );
- const dontElement = mount( );
-
- expect(doElement.find('ul').hasClass('nhsuk-list--tick')).toBeTruthy();
- expect(doElement.find('ul').hasClass('nhsuk-list--cross')).toBeFalsy();
-
- expect(dontElement.find('ul').hasClass('nhsuk-list--tick')).toBeFalsy();
- expect(dontElement.find('ul').hasClass('nhsuk-list--cross')).toBeTruthy();
-
- doElement.unmount();
- dontElement.unmount();
- });
-
- describe('DoDontList.Item', () => {
- it('matches snapshot', () => {
- const element = shallow(Text );
- expect(element).toMatchSnapshot('DoDontList.Item');
- element.unmount();
- });
-
- it('inherits type from context', () => {
- const doList = mount(
-
- Do
- ,
- );
- const dontList = mount(
-
- Don't
- ,
- );
-
- expect(doList.find('.nhsuk-icon__tick').exists()).toBeTruthy();
- expect(doList.find('.nhsuk-icon__cross').exists()).toBeFalsy();
-
- expect(dontList.find('.nhsuk-icon__tick').exists()).toBeFalsy();
- expect(dontList.find('.nhsuk-icon__cross').exists()).toBeTruthy();
-
- doList.unmount();
- dontList.unmount();
- });
-
- it("dont item includes 'do not' by default", () => {
- const dontList = mount(
-
- do something bad
- ,
- );
- expect(dontList.find('.nhsuk-list--cross').text()).toEqual('do not do something bad');
- dontList.unmount();
- });
-
- it('items render custom prefix text', () => {
- const doList = mount(
-
- something good 1
- something good 2
- also do }>something good 3
- something good 4
- something good 5
- ,
- );
- const dontList = mount(
-
- do something bad 1
- do something bad 2
- don't do }>
- something bad 3
-
- something bad 4
- something bad 5
- ,
- );
- expect(doList.find('.nhsuk-list--tick').childAt(0).text()).toBe('do something good 1');
- expect(doList.find('.nhsuk-list--tick').childAt(1).text()).toBe('something good 2');
- expect(doList.find('.nhsuk-list--tick').childAt(2).text()).toBe('also do something good 3');
- expect(doList.find('.nhsuk-list--tick').childAt(3).text()).toBe('something good 4');
- expect(doList.find('.nhsuk-list--tick').childAt(4).text()).toBe('something good 5');
-
- expect(dontList.find('.nhsuk-list--cross').childAt(0).text()).toBe(
- 'do not do something bad 1',
- );
- expect(dontList.find('.nhsuk-list--cross').childAt(1).text()).toBe(
- 'do not do something bad 2',
- );
- expect(dontList.find('.nhsuk-list--cross').childAt(2).text()).toBe(
- "don't do something bad 3",
- );
- expect(dontList.find('.nhsuk-list--cross').childAt(3).text()).toBe('do not something bad 4');
- expect(dontList.find('.nhsuk-list--cross').childAt(4).text()).toBe('something bad 5');
-
- doList.unmount();
- dontList.unmount();
- });
- });
-});
-
-describe('dont list dev warning', () => {
- jest.spyOn(console, 'warn').mockImplementation();
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should not warn when using do list', () => {
- const element = mount( );
- expect(console.warn).not.toHaveBeenCalled();
- element.unmount();
- });
-});
diff --git a/src/components/do-dont-list/__tests__/__snapshots__/DoDontList.test.tsx.snap b/src/components/do-dont-list/__tests__/__snapshots__/DoDontList.test.tsx.snap
deleted file mode 100644
index 8dd07a86..00000000
--- a/src/components/do-dont-list/__tests__/__snapshots__/DoDontList.test.tsx.snap
+++ /dev/null
@@ -1,46 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`DoDontList DoDontList.Item matches snapshot: DoDontList.Item 1`] = `
-
-
- Text
-
-`;
-
-exports[`DoDontList matches snapshot: DoDontList-Do 1`] = `
-
-`;
-
-exports[`DoDontList matches snapshot: DoDontList-Dont 1`] = `
-
-`;
diff --git a/src/components/do-dont-list/index.ts b/src/components/do-dont-list/index.ts
deleted file mode 100644
index 56c6e9cb..00000000
--- a/src/components/do-dont-list/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import DoDontList from './DoDontList';
-
-export default DoDontList;
diff --git a/src/components/error-message/__tests__/ErrorMessage.test.tsx b/src/components/error-message/__tests__/ErrorMessage.test.tsx
deleted file mode 100644
index 5d85277a..00000000
--- a/src/components/error-message/__tests__/ErrorMessage.test.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import ErrorMessage from '..';
-
-describe('ErrorMessage', () => {
- it('matches snapshot', () => {
- const element = shallow(Error );
- expect(element).toMatchSnapshot('ErrorMessage');
- element.unmount();
- });
-
- it('has default visuallyHiddenText', () => {
- const element = shallow(Error );
- expect(element.find('.nhsuk-u-visually-hidden').text()).toBe('Error: ');
- element.unmount();
- });
-
- it('has disabled visuallyHiddenText', () => {
- const element = shallow(Error );
- expect(element.find('.nhsuk-u-visually-hidden').exists()).toBeFalsy();
- element.unmount();
- });
-
- it('has custom visuallyHiddenText', () => {
- const element = shallow(Error );
- expect(element.find('.nhsuk-u-visually-hidden').text()).toBe('Custom');
- element.unmount();
- });
-});
diff --git a/src/components/error-message/__tests__/__snapshots__/ErrorMessage.test.tsx.snap b/src/components/error-message/__tests__/__snapshots__/ErrorMessage.test.tsx.snap
deleted file mode 100644
index 4b68d81f..00000000
--- a/src/components/error-message/__tests__/__snapshots__/ErrorMessage.test.tsx.snap
+++ /dev/null
@@ -1,15 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ErrorMessage matches snapshot: ErrorMessage 1`] = `
-
-
- Error:
-
- Error
-
-`;
diff --git a/src/components/error-summary/ErrorSummary.tsx b/src/components/error-summary/ErrorSummary.tsx
deleted file mode 100644
index eb82250c..00000000
--- a/src/components/error-summary/ErrorSummary.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, {forwardRef, HTMLProps, PropsWithoutRef, RefAttributes} from 'react';
-import classNames from 'classnames';
-
-const ErrorSummaryTitle: React.FC> = ({ className, ...rest }) => (
-
-);
-
-const ErrorSummaryBody: React.FC> = ({ className, ...rest }) => (
-
-);
-
-const ErrorSummaryList: React.FC> = ({ className, ...rest }) => (
-
-);
-
-const ErrorSummaryListItem: React.FC> = (props) => (
-
-
-
-);
-
-interface ErrorSummary extends React.ForwardRefExoticComponent> & RefAttributes> {
- Title: React.FC>;
- Body: React.FC>;
- List: React.FC>;
- Item: React.FC>;
-}
-
-const ErrorSummaryDiv = forwardRef>(({className, ...rest}, ref) =>
-
-)
-ErrorSummaryDiv.displayName = "ErrorSummary"
-
-const ErrorSummary: ErrorSummary = Object.assign(ErrorSummaryDiv, {
- Title: ErrorSummaryTitle,
- Body: ErrorSummaryBody,
- List: ErrorSummaryList,
- Item: ErrorSummaryListItem,
-})
-
-export default ErrorSummary;
diff --git a/src/components/error-summary/__tests__/ErrorSummary.test.tsx b/src/components/error-summary/__tests__/ErrorSummary.test.tsx
deleted file mode 100644
index 05b39055..00000000
--- a/src/components/error-summary/__tests__/ErrorSummary.test.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import { mount, shallow } from 'enzyme';
-import ErrorSummary from '..';
-
-describe('ErrorSummary', () => {
- it('matches snapshot', () => {
- const element = shallow( );
- expect(element).toMatchSnapshot('ErrorSummary');
- element.unmount();
- });
-
- it('forwards refs', () => {
- const ref = React.createRef();
- const element = mount();
- expect(ref.current).not.toBeNull();
- element.unmount();
- });
-
- describe('ErrorSummary.Title', () => {
- it('matches snapshot', () => {
- const element = shallow(Title );
- expect(element.text()).toBe('Title');
- expect(element).toMatchSnapshot('ErrorSummary.Title');
- element.unmount();
- });
- });
-
- describe('ErrorSummary.Body', () => {
- it('matches snapshot', () => {
- const element = shallow(Body );
- expect(element.text()).toBe('Body');
- expect(element).toMatchSnapshot('ErrorSummary.Body');
- element.unmount();
- });
- });
-
- describe('ErrorSummary.List', () => {
- it('matches snapshot', () => {
- const element = shallow(List );
- expect(element.text()).toBe('List');
- expect(element).toMatchSnapshot('ErrorSummary.List');
- element.unmount();
- });
- });
-
- describe('ErrorSummary.ListItem', () => {
- it('matches snapshot', () => {
- const element = shallow(ListItem );
- expect(element.find('a').text()).toBe('ListItem');
- expect(element).toMatchSnapshot('ErrorSummary.ListItem');
- element.unmount();
- });
- });
-});
diff --git a/src/components/fieldset/Fieldset.tsx b/src/components/fieldset/Fieldset.tsx
deleted file mode 100644
index 5045f939..00000000
--- a/src/components/fieldset/Fieldset.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import React, { HTMLProps, PureComponent } from 'react';
-import classNames from 'classnames';
-import { NHSUKSize } from '../../util/types/NHSUKTypes';
-import HeadingLevel, { HeadingLevelType } from '../../util/HeadingLevel';
-import FieldsetContext, { IFieldsetContext } from './FieldsetContext';
-
-interface LegendProps extends Omit, 'size'> {
- isPageHeading?: boolean;
- headingLevel?: HeadingLevelType;
- size?: NHSUKSize;
-}
-
-const Legend: React.FC = ({
- className,
- children,
- isPageHeading,
- headingLevel,
- size,
- ...rest
-}) => (
-
- {isPageHeading ? (
-
- {children}
-
- ) : (
- children
- )}
-
-);
-
-Legend.defaultProps = {
- headingLevel: 'h1',
-};
-
-interface FieldsetProps extends HTMLProps {
- disableErrorLine?: boolean;
-}
-
-type FieldsetState = { registeredComponents: Array; erroredComponents: Array };
-
-class Fieldset extends PureComponent {
- static Legend = Legend;
-
- constructor(props: FieldsetProps) {
- super(props);
- this.state = {
- registeredComponents: [],
- erroredComponents: [],
- };
- }
-
- passError = (componentId: string, error: boolean): void => {
- this.setState((state) => {
- const existingError = state.erroredComponents.includes(componentId);
- if (existingError && !error) {
- return {
- ...state,
- erroredComponents: state.erroredComponents.filter((id) => id !== componentId),
- };
- }
- if (!existingError && error) {
- return { ...state, erroredComponents: [...state.erroredComponents, componentId] };
- }
- return state;
- });
- };
-
- registerComponent = (componentId: string, deregister = false): void => {
- this.setState((state) => {
- if (deregister) {
- return {
- ...state,
- registeredComponents: state.registeredComponents.filter((id) => id !== componentId),
- };
- }
- if (!state.registeredComponents.includes(componentId)) {
- return {
- ...state,
- registeredComponents: [...state.registeredComponents, componentId],
- };
- }
- return state;
- });
- };
-
- render(): JSX.Element {
- const { className, disableErrorLine, ...rest } = this.props;
- const contextValue: IFieldsetContext = {
- isFieldset: true,
- registerComponent: this.registerComponent,
- passError: this.passError,
- };
-
- const containsFormElements = this.state.registeredComponents.length > 0;
- const containsError = this.state.erroredComponents.length > 0;
-
- return (
-
- {containsFormElements ? (
-
-
-
- ) : (
-
- )}
-
- );
- }
-}
-
-Fieldset.Legend = Legend;
-
-export default Fieldset;
diff --git a/src/components/fieldset/__tests__/Fieldset.test.tsx b/src/components/fieldset/__tests__/Fieldset.test.tsx
deleted file mode 100644
index 9624b76e..00000000
--- a/src/components/fieldset/__tests__/Fieldset.test.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import Fieldset from '..';
-
-describe('Fieldset', () => {
- it('matches snapshot', () => {
- const element = shallow(Text );
- expect(element.text()).toBe('Text');
- expect(element).toMatchSnapshot('Fieldset');
- element.unmount();
- });
-
- describe('Fieldset.Legend', () => {
- it('matches snapshot', () => {
- const element = shallow(Text );
- expect(element).toMatchSnapshot('FieldsetLegend');
- element.unmount();
- });
-
- it('renders as page heading', () => {
- const element = shallow(Text );
- expect(element.hasClass('nhsuk-fieldset__legend--xl')).toBeTruthy();
- expect(
- element
- .find('.nhsuk-fieldset__heading')
- .render()
- .text(),
- ).toBe('Text');
- element.unmount();
- });
- });
-});
diff --git a/src/components/fieldset/__tests__/__snapshots__/Fieldset.test.tsx.snap b/src/components/fieldset/__tests__/__snapshots__/Fieldset.test.tsx.snap
deleted file mode 100644
index 432939da..00000000
--- a/src/components/fieldset/__tests__/__snapshots__/Fieldset.test.tsx.snap
+++ /dev/null
@@ -1,27 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Fieldset Fieldset.Legend matches snapshot: FieldsetLegend 1`] = `
-
- Text
-
-`;
-
-exports[`Fieldset matches snapshot: Fieldset 1`] = `
-
-
- Text
-
-
-`;
diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx
deleted file mode 100644
index 616df350..00000000
--- a/src/components/footer/Footer.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { HTMLProps } from 'react';
-import classNames from 'classnames';
-import { Container } from '../layout';
-
-type FooterListProps = HTMLProps;
-
-const FooterList: React.FC = ({ className, ...rest }) => (
-
-);
-
-const FooterListItem: React.FC> = ({ className, ...rest }) => (
-
-
-
-);
-
-const FooterCopyright: React.FC> = ({ className, ...rest }) => (
-
-);
-
-interface FooterProps extends HTMLProps {
- visuallyHiddenText?: false | string;
-}
-
-interface Footer extends React.FC {
- List: React.FC;
- ListItem: React.FC>;
- Copyright: React.FC>;
-}
-
-const Footer: Footer = ({ className, children, visuallyHiddenText, ...rest }) => (
-
-);
-
-Footer.defaultProps = {
- visuallyHiddenText: 'Support links',
-};
-
-Footer.List = FooterList;
-Footer.ListItem = FooterListItem;
-Footer.Copyright = FooterCopyright;
-
-export default Footer;
diff --git a/src/components/footer/__tests__/Footer.test.tsx b/src/components/footer/__tests__/Footer.test.tsx
deleted file mode 100644
index 290e44a9..00000000
--- a/src/components/footer/__tests__/Footer.test.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import { shallow, mount } from 'enzyme';
-import Footer from '..';
-
-jest.spyOn(console, 'warn').mockImplementation();
-
-describe('Footer', () => {
- it('matches snapshot', () => {
- const component = shallow();
- expect(component).toMatchSnapshot('Footer');
- component.unmount();
- });
- it('has default visually hidden text', () => {
- const component = mount();
- expect(component.find('.nhsuk-u-visually-hidden').text()).toBe('Support links');
- component.unmount();
- });
-
- it('has disabled visually hidden text', () => {
- const component = mount();
- expect(component.find('.nhsuk-u-visually-hidden').exists()).toBeFalsy();
- component.unmount();
- });
-
- it('has custom visually hidden text', () => {
- const component = mount();
- expect(component.find('.nhsuk-u-visually-hidden').text()).toBe('Custom');
- component.unmount();
- });
-
- describe('Footer.List', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('matches snapshot', () => {
- const component = shallow( );
- expect(component).toMatchSnapshot('Footer.List');
- component.unmount();
- });
- });
-
- describe('Footer.ListItem', () => {
- it('matches snapshot', () => {
- const component = shallow( );
- expect(component).toMatchSnapshot('Footer.ListItem');
- component.unmount();
- });
- });
-
- describe('Footer.Copyright', () => {
- it('matches snapshot', () => {
- const component = shallow( );
- expect(component).toMatchSnapshot('Footer.Copyright');
- component.unmount();
- });
- });
-});
diff --git a/src/components/footer/__tests__/__snapshots__/Footer.test.tsx.snap b/src/components/footer/__tests__/__snapshots__/Footer.test.tsx.snap
deleted file mode 100644
index d3e22b71..00000000
--- a/src/components/footer/__tests__/__snapshots__/Footer.test.tsx.snap
+++ /dev/null
@@ -1,39 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Footer Footer.Copyright matches snapshot: Footer.Copyright 1`] = `
-
-`;
-
-exports[`Footer Footer.List matches snapshot: Footer.List 1`] = `
-
-`;
-
-exports[`Footer Footer.ListItem matches snapshot: Footer.ListItem 1`] = `
-
-
-
-`;
-
-exports[`Footer matches snapshot: Footer 1`] = `
-
-`;
diff --git a/src/components/button/Button.tsx b/src/components/form-elements/button/Button.tsx
similarity index 74%
rename from src/components/button/Button.tsx
rename to src/components/form-elements/button/Button.tsx
index 75d1f22b..333e5c4d 100644
--- a/src/components/button/Button.tsx
+++ b/src/components/form-elements/button/Button.tsx
@@ -1,7 +1,7 @@
-import React, { HTMLProps } from 'react';
+import React, { FC, HTMLProps } from 'react';
import classNames from 'classnames';
-interface ButtonProps extends HTMLProps {
+export interface ButtonProps extends HTMLProps {
type?: 'button' | 'submit' | 'reset';
disabled?: boolean;
secondary?: boolean;
@@ -9,18 +9,19 @@ interface ButtonProps extends HTMLProps {
as?: 'button';
}
-interface ButtonLinkProps extends HTMLProps {
+export interface ButtonLinkProps extends HTMLProps {
disabled?: boolean;
secondary?: boolean;
reverse?: boolean;
as?: 'a';
}
-export const Button: React.FC = ({
+export const Button: FC = ({
className,
disabled,
secondary,
reverse,
+ type = 'submit',
...rest
}) => (
// eslint-disable-next-line react/button-has-type
@@ -34,18 +35,15 @@ export const Button: React.FC = ({
)}
disabled={disabled}
aria-disabled={disabled ? 'true' : 'false'}
+ type={type}
{...rest}
/>
);
-Button.defaultProps = {
- type: 'submit',
-};
-
-export const ButtonLink: React.FC = ({
+export const ButtonLink: FC = ({
className,
- role,
- draggable,
+ role = 'button',
+ draggable = false,
children,
disabled,
secondary,
@@ -69,12 +67,7 @@ export const ButtonLink: React.FC = ({
);
-ButtonLink.defaultProps = {
- role: 'button',
- draggable: false,
-};
-
-const ButtonWrapper: React.FC = ({ href, as, ...rest }) => {
+const ButtonWrapper: FC = ({ href, as, ...rest }) => {
if (as === 'a') {
return ;
}
diff --git a/src/components/form-elements/button/__tests__/Button.test.tsx b/src/components/form-elements/button/__tests__/Button.test.tsx
new file mode 100644
index 00000000..97c88989
--- /dev/null
+++ b/src/components/form-elements/button/__tests__/Button.test.tsx
@@ -0,0 +1,186 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import ButtonWrapper, { ButtonLink, Button } from '../Button';
+
+describe('Button', () => {
+ it('matches snapshot', () => {
+ const { container } = render(Submit );
+
+ expect(container).toMatchSnapshot('PlainButton');
+ });
+
+ it('renders child text as expected', () => {
+ const { container } = render(Submit );
+
+ expect(container.querySelector('button')?.textContent).toEqual('Submit');
+ });
+
+ describe('disabled', () => {
+ it('matches snapshot', () => {
+ const { container } = render(Submit );
+
+ expect(container).toMatchSnapshot('DisabledButton');
+ });
+
+ it('adds correct classes for button type', () => {
+ const { container } = render(Submit );
+
+ expect(container.querySelector('.nhsuk-button--disabled')).toBeTruthy();
+ });
+ });
+
+ describe('secondary', () => {
+ it('matches snapshot', () => {
+ const { container } = render(Submit );
+
+ expect(container).toMatchSnapshot('SecondaryButton');
+ });
+
+ it('adds correct classes for button type', () => {
+ const { container } = render(Submit );
+
+ expect(container.querySelector('.nhsuk-button--secondary')).toBeTruthy();
+ });
+ });
+
+ describe('reverse', () => {
+ it('matches snapshot', () => {
+ const { container } = render(Submit );
+
+ expect(container).toMatchSnapshot('ReverseButton');
+ });
+
+ it('adds correct classes for button type', () => {
+ const { container } = render(Submit );
+
+ expect(container.querySelector('.nhsuk-button--reverse')).toBeTruthy();
+ });
+ });
+
+ it('adds aria props and disabled to disabled button', () => {
+ const { container } = render(Submit );
+
+ expect(
+ container.querySelector('button.nhsuk-button.nhsuk-button--disabled')?.getAttribute('type'),
+ ).toBe('submit');
+ expect(
+ container
+ .querySelector('button.nhsuk-button.nhsuk-button--disabled')
+ ?.getAttribute('aria-disabled'),
+ ).toBe('true');
+ expect(container.querySelector('button.nhsuk-button.nhsuk-button--disabled')).toBeDisabled();
+ });
+});
+
+describe('ButtonLink', () => {
+ it('matches snapshot', () => {
+ const { container } = render(Submit );
+
+ expect(container).toMatchSnapshot('PlainButton');
+ });
+
+ it('renders child text as expected', () => {
+ const { container } = render(Submit );
+
+ expect(container.querySelector('a')?.textContent).toEqual('Submit');
+ });
+
+ describe('button types', () => {
+ describe('disabled', () => {
+ it('matches snapshot', () => {
+ const { container } = render(
+
+ Submit
+ ,
+ );
+
+ expect(container).toMatchSnapshot('DisabledButton');
+ });
+
+ it('adds correct classes for type - disabled', () => {
+ const { container } = render(
+
+ Submit
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-button--disabled')).toBeTruthy();
+ });
+ });
+
+ describe('secondary', () => {
+ it('matches snapshot', () => {
+ const { container } = render(
+
+ Submit
+ ,
+ );
+
+ expect(container).toMatchSnapshot('SecondaryButton');
+ });
+
+ it('adds correct classes for type - secondary', () => {
+ const { container } = render(
+
+ Submit
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-button--secondary')).toBeTruthy();
+ });
+ });
+
+ describe('reverse', () => {
+ it('matches snapshot', () => {
+ const { container } = render(
+
+ Submit
+ ,
+ );
+
+ expect(container).toMatchSnapshot('ReverseButton');
+ });
+
+ it('adds correct classes for type - reverse', () => {
+ const { container } = render(
+
+ Submit
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-button--reverse')).toBeTruthy();
+ });
+ });
+ });
+
+ it('adds aria disabled props to disabled button', () => {
+ const { container } = render(
+
+ Submit
+ ,
+ );
+
+ expect(
+ container.querySelector('a.nhsuk-button.nhsuk-button--disabled')?.getAttribute('role'),
+ ).toBe('button');
+ expect(
+ container
+ .querySelector('a.nhsuk-button.nhsuk-button--disabled')
+ ?.getAttribute('aria-disabled'),
+ ).toBe('true');
+ });
+});
+
+describe('ButtonWrapper', () => {
+ it('renders a button when not given a href', () => {
+ const { container } = render(Submit );
+
+ expect(container.querySelector('button.nhsuk-button')?.textContent).toBe('Submit');
+ });
+
+ it('renders an anchor when given a href', () => {
+ const { container } = render(Submit );
+
+ expect(container.querySelector('a.nhsuk-button')?.textContent).toBe('Submit');
+ });
+});
diff --git a/src/components/form-elements/button/__tests__/__snapshots__/Button.test.tsx.snap b/src/components/form-elements/button/__tests__/__snapshots__/Button.test.tsx.snap
new file mode 100644
index 00000000..4afeb8e1
--- /dev/null
+++ b/src/components/form-elements/button/__tests__/__snapshots__/Button.test.tsx.snap
@@ -0,0 +1,106 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Button disabled matches snapshot: DisabledButton 1`] = `
+
+
+ Submit
+
+
+`;
+
+exports[`Button matches snapshot: PlainButton 1`] = `
+
+
+ Submit
+
+
+`;
+
+exports[`Button reverse matches snapshot: ReverseButton 1`] = `
+
+
+ Submit
+
+
+`;
+
+exports[`Button secondary matches snapshot: SecondaryButton 1`] = `
+
+
+ Submit
+
+
+`;
+
+exports[`ButtonLink button types disabled matches snapshot: DisabledButton 1`] = `
+
+`;
+
+exports[`ButtonLink button types reverse matches snapshot: ReverseButton 1`] = `
+
+`;
+
+exports[`ButtonLink button types secondary matches snapshot: SecondaryButton 1`] = `
+
+`;
+
+exports[`ButtonLink matches snapshot: PlainButton 1`] = `
+
+`;
diff --git a/src/components/button/index.ts b/src/components/form-elements/button/index.ts
similarity index 100%
rename from src/components/button/index.ts
rename to src/components/form-elements/button/index.ts
diff --git a/src/components/form-elements/character-count/CharacterCount.tsx b/src/components/form-elements/character-count/CharacterCount.tsx
new file mode 100644
index 00000000..e19ca0e9
--- /dev/null
+++ b/src/components/form-elements/character-count/CharacterCount.tsx
@@ -0,0 +1,55 @@
+'use client';
+import React, { FC, useEffect } from 'react';
+import CharacterCountJs from '@resources/character-count';
+import { HTMLAttributesWithData } from '@util/types/NHSUKTypes';
+
+export enum CharacterCountType {
+ Characters,
+ Words,
+}
+
+type CharacterCountProps = React.HTMLAttributes & {
+ children: React.ReactNode;
+ maxLength: number;
+ countType: CharacterCountType;
+ textAreaId: string;
+ thresholdPercent?: number;
+};
+
+const CharacterCount: FC = ({
+ children,
+ maxLength,
+ countType,
+ textAreaId,
+ thresholdPercent,
+ ...rest
+}) => {
+ useEffect(() => {
+ CharacterCountJs();
+ }, []);
+
+ const characterCountProps: HTMLAttributesWithData =
+ countType === CharacterCountType.Characters
+ ? { ...rest, ['data-maxlength']: maxLength }
+ : { ...rest, ['data-maxwords']: maxLength };
+
+ if (thresholdPercent) {
+ characterCountProps['data-threshold'] = thresholdPercent;
+ }
+
+ return (
+
+
{children}
+
+
+ You can enter up to {maxLength} characters
+
+
+ );
+};
+
+export default CharacterCount;
diff --git a/src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx b/src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx
new file mode 100644
index 00000000..a6773583
--- /dev/null
+++ b/src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx
@@ -0,0 +1,89 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import CharacterCount, { CharacterCountType } from '../CharacterCount';
+import Label from '@components/form-elements/label/Label';
+import HintText from '@components/form-elements/hint-text/HintText';
+import Textarea from '@components/form-elements/textarea/Textarea';
+
+describe('Character Count', () => {
+ it('Matches snapshot', () => {
+ const { container } = render(
+
+ Can you provide more detail?
+
+ Do not include personal information like your name, date of birth or NHS number.
+
+
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('Sets the data-maxlength attribute when counting characters', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-character-count')?.getAttribute('data-maxlength')).toBe(
+ '200',
+ );
+ expect(
+ container.querySelector('.nhsuk-character-count')?.getAttribute('data-maxwords'),
+ ).toBeNull();
+ expect(
+ container.querySelector('.nhsuk-character-count')?.getAttribute('data-threshold'),
+ ).toBeNull();
+ });
+
+ it('Sets the data-maxwords attribute when counting words', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-character-count')?.getAttribute('data-maxwords')).toBe(
+ '200',
+ );
+ expect(
+ container.querySelector('.nhsuk-character-count')?.getAttribute('data-maxlength'),
+ ).toBeNull();
+ expect(
+ container.querySelector('.nhsuk-character-count')?.getAttribute('data-threshold'),
+ ).toBeNull();
+ });
+
+ it('Sets the data-threshold attribute when threshold is specified', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-character-count')?.getAttribute('data-threshold')).toBe(
+ '50',
+ );
+ });
+});
diff --git a/src/components/form-elements/character-count/__tests__/__snapshots__/CharacterCount.test.tsx.snap b/src/components/form-elements/character-count/__tests__/__snapshots__/CharacterCount.test.tsx.snap
new file mode 100644
index 00000000..2356a676
--- /dev/null
+++ b/src/components/form-elements/character-count/__tests__/__snapshots__/CharacterCount.test.tsx.snap
@@ -0,0 +1,59 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Character Count Matches snapshot 1`] = `
+
+`;
diff --git a/src/components/form-elements/character-count/index.ts b/src/components/form-elements/character-count/index.ts
new file mode 100644
index 00000000..a4cba558
--- /dev/null
+++ b/src/components/form-elements/character-count/index.ts
@@ -0,0 +1,4 @@
+import CharacterCount, { CharacterCountType } from './CharacterCount';
+
+export { CharacterCountType };
+export default CharacterCount;
diff --git a/src/components/checkboxes/CheckboxContext.ts b/src/components/form-elements/checkboxes/CheckboxContext.ts
similarity index 81%
rename from src/components/checkboxes/CheckboxContext.ts
rename to src/components/form-elements/checkboxes/CheckboxContext.ts
index 7770e8af..8e3aec8d 100644
--- a/src/components/checkboxes/CheckboxContext.ts
+++ b/src/components/form-elements/checkboxes/CheckboxContext.ts
@@ -3,7 +3,6 @@ import { createContext } from 'react';
export interface ICheckboxContext {
name: string;
getBoxId: (reference: string) => string | undefined;
- setConditional: (boxReference: string, hasConditional: boolean) => void;
leaseReference: () => string;
unleaseReference: (reference: string) => void;
}
@@ -12,7 +11,6 @@ export default createContext({
/* eslint-disable @typescript-eslint/no-empty-function */
name: '',
getBoxId: () => undefined,
- setConditional: () => {},
leaseReference: () => '',
unleaseReference: () => {},
});
diff --git a/src/components/form-elements/checkboxes/Checkboxes.tsx b/src/components/form-elements/checkboxes/Checkboxes.tsx
new file mode 100644
index 00000000..b2b1083e
--- /dev/null
+++ b/src/components/form-elements/checkboxes/Checkboxes.tsx
@@ -0,0 +1,79 @@
+'use client';
+
+import React, { HTMLProps, useEffect } from 'react';
+import classNames from 'classnames';
+import { FormElementProps } from '@util/types/FormTypes';
+import FormGroup from '@util/FormGroup';
+import CheckboxContext, { ICheckboxContext } from './CheckboxContext';
+import Box from './components/Box';
+import Divider from './components/Divider';
+import { generateRandomName } from '@util/RandomID';
+import CheckboxJs from '@resources/checkboxes';
+
+interface CheckboxesProps extends HTMLProps, FormElementProps {
+ idPrefix?: string;
+}
+
+const Checkboxes = ({ children, idPrefix, ...rest }: CheckboxesProps) => {
+ const _boxReferences: string[] = [];
+ let _boxCount: number = 0;
+ let _boxIds: Record = {};
+
+ useEffect(() => {
+ CheckboxJs();
+ }, []);
+
+ const getBoxId = (id: string, reference: string): string => {
+ if (reference in _boxIds) {
+ return _boxIds[reference];
+ }
+ _boxCount++;
+ _boxIds[reference] = `${idPrefix ?? id}-${_boxCount}`;
+
+ return _boxIds[reference];
+ };
+
+ const leaseReference = (): string => {
+ let reference: string = '';
+ do {
+ reference = generateRandomName();
+ } while (_boxReferences.includes(reference));
+
+ _boxReferences.push(reference);
+ return reference;
+ };
+
+ const unleaseReference = (reference: string): void => {
+ _boxReferences.splice(_boxReferences.indexOf(reference), 1);
+ };
+
+ const resetCheckboxIds = (): void => {
+ _boxCount = 0;
+ _boxIds = {};
+ };
+
+ return (
+ inputType="checkboxes" {...rest}>
+ {/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
+ {({ className, name, id, idPrefix, error, ...restRenderProps }) => {
+ resetCheckboxIds();
+ const contextValue: ICheckboxContext = {
+ name,
+ getBoxId: (reference) => getBoxId(id, reference),
+ leaseReference,
+ unleaseReference,
+ };
+ return (
+
+ {children}
+
+ );
+ }}
+
+ );
+};
+
+Checkboxes.Box = Box;
+Checkboxes.Divider = Divider;
+
+export default Checkboxes;
diff --git a/src/components/form-elements/checkboxes/__tests__/Checkboxes.test.tsx b/src/components/form-elements/checkboxes/__tests__/Checkboxes.test.tsx
new file mode 100644
index 00000000..ced26677
--- /dev/null
+++ b/src/components/form-elements/checkboxes/__tests__/Checkboxes.test.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import Checkboxes from '../';
+
+describe('Checkboxes', () => {
+ it('matches snapshot', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('matches snapshot with string error', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('matches snapshot with boolean error', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('Matches the snapshot with an exclusive checkbox', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('Sets a data-exclusive attribute when exclusive is true for a box', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container.querySelector('#none')?.getAttribute('data-checkbox-exclusive')).toBe('true');
+ });
+});
diff --git a/src/components/form-elements/checkboxes/__tests__/__snapshots__/Checkboxes.test.tsx.snap b/src/components/form-elements/checkboxes/__tests__/__snapshots__/Checkboxes.test.tsx.snap
new file mode 100644
index 00000000..b6804bd1
--- /dev/null
+++ b/src/components/form-elements/checkboxes/__tests__/__snapshots__/Checkboxes.test.tsx.snap
@@ -0,0 +1,330 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Checkboxes Matches the snapshot with an exclusive checkbox 1`] = `
+
+`;
+
+exports[`Checkboxes matches snapshot 1`] = `
+
+`;
+
+exports[`Checkboxes matches snapshot with boolean error 1`] = `
+
+`;
+
+exports[`Checkboxes matches snapshot with string error 1`] = `
+
+`;
diff --git a/src/components/checkboxes/components/Box.tsx b/src/components/form-elements/checkboxes/components/Box.tsx
similarity index 77%
rename from src/components/checkboxes/components/Box.tsx
rename to src/components/form-elements/checkboxes/components/Box.tsx
index f4da8b69..3c576c57 100644
--- a/src/components/checkboxes/components/Box.tsx
+++ b/src/components/form-elements/checkboxes/components/Box.tsx
@@ -1,4 +1,7 @@
+'use client';
+
import React, {
+ FC,
HTMLProps,
useContext,
ReactNode,
@@ -9,19 +12,21 @@ import React, {
import classNames from 'classnames';
import CheckboxContext, { ICheckboxContext } from '../CheckboxContext';
import Label, { LabelProps } from '../../label/Label';
-import Hint, { HintProps } from '../../hint/Hint';
+import HintText, { HintTextProps } from '../../hint-text/HintText';
+import { HTMLAttributesWithData } from '@util/types/NHSUKTypes';
type BoxProps = Omit, 'label'> & {
labelProps?: LabelProps;
hint?: string;
- hintProps?: HintProps;
+ hintProps?: HintTextProps;
conditional?: ReactNode;
forceShowConditional?: boolean;
conditionalWrapperProps?: HTMLProps;
inputRef?: MutableRefObject;
+ exclusive?: boolean;
};
-const Box: React.FC = ({
+const Box: FC = ({
id,
labelProps,
children,
@@ -34,9 +39,11 @@ const Box: React.FC = ({
inputRef,
forceShowConditional,
conditionalWrapperProps,
+ exclusive = false,
+ type = 'checkbox',
...rest
}) => {
- const { getBoxId, name, setConditional, unleaseReference, leaseReference } =
+ const { getBoxId, name, leaseReference, unleaseReference } =
useContext(CheckboxContext);
const [boxReference] = useState(leaseReference());
@@ -56,10 +63,11 @@ const Box: React.FC = ({
}
}, [checked]);
- useEffect(() => {
- setConditional(boxReference, Boolean(conditional));
- return () => setConditional(boxReference, false);
- }, [conditional]);
+ const inputProps: HTMLAttributesWithData = rest;
+
+ if (exclusive) {
+ inputProps['data-checkbox-exclusive'] = true;
+ }
return (
<>
@@ -75,7 +83,9 @@ const Box: React.FC = ({
checked={checked}
defaultChecked={defaultChecked}
ref={inputRef}
- {...rest}
+ type={type}
+ data-checkbox-exclusive-group={name}
+ {...inputProps}
/>
{children ? (
= ({
) : null}
{hint ? (
-
+
{hint}
-
+
) : null}
{conditional && (showConditional || forceShowConditional) ? (
@@ -106,8 +119,4 @@ const Box: React.FC = ({
);
};
-Box.defaultProps = {
- type: 'checkbox',
-};
-
export default Box;
diff --git a/src/components/form-elements/checkboxes/components/Divider.tsx b/src/components/form-elements/checkboxes/components/Divider.tsx
new file mode 100644
index 00000000..547aa302
--- /dev/null
+++ b/src/components/form-elements/checkboxes/components/Divider.tsx
@@ -0,0 +1,11 @@
+import React, { FC } from 'react';
+
+type DividerProps = {
+ dividerText?: string;
+};
+
+const Divider: FC = ({ dividerText = 'or' }) => (
+ {dividerText}
+);
+
+export default Divider;
diff --git a/src/components/checkboxes/index.ts b/src/components/form-elements/checkboxes/index.ts
similarity index 100%
rename from src/components/checkboxes/index.ts
rename to src/components/form-elements/checkboxes/index.ts
diff --git a/src/components/form-elements/date-input/DateInput.tsx b/src/components/form-elements/date-input/DateInput.tsx
new file mode 100644
index 00000000..a4f8adb3
--- /dev/null
+++ b/src/components/form-elements/date-input/DateInput.tsx
@@ -0,0 +1,127 @@
+'use client';
+
+import React, { HTMLProps, ChangeEvent, useEffect, useState } from 'react';
+import classNames from 'classnames';
+import { DayInput, MonthInput, YearInput } from './components/IndividualDateInputs';
+import FormGroup from '@util/FormGroup';
+import DateInputContext, { IDateInputContext } from './DateInputContext';
+import { FormElementProps } from '@util/types/FormTypes';
+
+type DateInputValue = {
+ day: string;
+ month: string;
+ year: string;
+};
+
+export type DateInputChangeEvent = ChangeEvent & {
+ target: HTMLInputElement & { value: DateInputValue };
+ currentTarget: HTMLInputElement & { value: DateInputValue };
+};
+
+interface DateInputProps
+ extends Omit, 'value' | 'defaultValue'>,
+ FormElementProps {
+ autoSelectNext?: boolean;
+ value?: Partial;
+ defaultValue?: Partial;
+ onChange?: (e: DateInputChangeEvent) => void;
+}
+
+type InputType = 'day' | 'month' | 'year';
+
+const DateInput = ({
+ autoSelectNext,
+ children,
+ onChange,
+ value,
+ defaultValue,
+ ...rest
+}: DateInputProps) => {
+ let monthRef: HTMLInputElement | null = null;
+ let yearRef: HTMLInputElement | null = null;
+ const [internalDate, setInternalDate] = useState>({
+ day: value?.day ?? '',
+ month: value?.month ?? '',
+ year: value?.year ?? '',
+ });
+
+ useEffect(() => {
+ const newState = { ...internalDate };
+ const { day, month, year } = value ?? {};
+ if (day && day !== internalDate.day) newState.day = day;
+ if (month && month !== internalDate.month) newState.month = month;
+ if (year && year !== internalDate.year) newState.year = year;
+
+ return setInternalDate(newState);
+ }, [value]);
+
+ const handleFocusNextInput = (inputType: InputType, value: string): void => {
+ if (!autoSelectNext) return;
+ if (inputType === 'day' && value.length === 2 && monthRef) {
+ monthRef.focus();
+ } else if (inputType === 'month' && value.length === 2 && yearRef) {
+ yearRef.focus();
+ }
+ };
+
+ const handleChange = (inputType: InputType, event: ChangeEvent): void => {
+ handleFocusNextInput(inputType, event.target.value);
+ event.stopPropagation();
+
+ if (onChange) {
+ const newEventValue = {
+ ...internalDate,
+ [inputType]: event.target.value,
+ };
+ const newEvent = {
+ ...event,
+ target: { ...event.target, value: newEventValue },
+ currentTarget: { ...event.currentTarget, value: newEventValue },
+ } as DateInputChangeEvent;
+
+ onChange(newEvent);
+ setInternalDate(newEventValue);
+ }
+ };
+
+ const registerRef = (inputType: InputType, ref: HTMLInputElement | null): void => {
+ if (inputType === 'month') monthRef = ref;
+ if (inputType === 'year') yearRef = ref;
+ };
+
+ return (
+ > inputType="dateinput" {...rest}>
+ {/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
+ {({ className, name, id, error, autoSelectNext, ...restRenderProps }) => {
+ const contextValue: IDateInputContext = {
+ id,
+ name,
+ error,
+ value,
+ defaultValue,
+ handleChange,
+ registerRef,
+ };
+ return (
+
+
+ {children || (
+ <>
+
+
+
+ >
+ )}
+
+
+ );
+ }}
+
+ );
+};
+
+DateInput.Day = DayInput;
+DateInput.Month = MonthInput;
+DateInput.Year = YearInput;
+
+export default DateInput;
diff --git a/src/components/date-input/DateInputContext.ts b/src/components/form-elements/date-input/DateInputContext.ts
similarity index 100%
rename from src/components/date-input/DateInputContext.ts
rename to src/components/form-elements/date-input/DateInputContext.ts
diff --git a/src/components/form-elements/date-input/__tests__/DateInput.test.tsx b/src/components/form-elements/date-input/__tests__/DateInput.test.tsx
new file mode 100644
index 00000000..83a4d424
--- /dev/null
+++ b/src/components/form-elements/date-input/__tests__/DateInput.test.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import DateInput, { DateInputChangeEvent } from '../DateInput';
+
+describe('DateInput', () => {
+ it('matches snapshot', () => {
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it.each`
+ autoSelectNext | inputValue | monthFocusExpected
+ ${false} | ${'1'} | ${false}
+ ${false} | ${'11'} | ${false}
+ ${true} | ${'1'} | ${false}
+ ${true} | ${'11'} | ${true}
+ `(
+ 'When autoSelectNext is $autoSelectNext, the day input value is $inputValue, then month focus is expected to be $monthFocusExpected',
+ ({ autoSelectNext, inputValue, monthFocusExpected }) => {
+ const { container } = render(
+ ,
+ );
+
+ const dayInput = container.querySelector('#testInput-day')!;
+ const monthInput = container.querySelector('#testInput-month')!;
+
+ expect(monthInput).not.toHaveFocus();
+
+ fireEvent.change(dayInput, { target: { value: inputValue } });
+
+ if (monthFocusExpected) {
+ expect(monthInput).toHaveFocus();
+ } else {
+ expect(monthInput).not.toHaveFocus();
+ }
+ },
+ );
+
+ it.each`
+ autoSelectNext | inputValue | yearFocusExpected
+ ${false} | ${'1'} | ${false}
+ ${false} | ${'11'} | ${false}
+ ${true} | ${'1'} | ${false}
+ ${true} | ${'11'} | ${true}
+ `(
+ 'When autoSelectNext is $autoSelectNext, the day input value is $inputValue, then year focus is expected to be $yearFocusExpected',
+ ({ autoSelectNext, inputValue, yearFocusExpected }) => {
+ const { container } = render(
+ ,
+ );
+
+ const monthInput = container.querySelector('#testInput-month')!;
+ const yearInput = container.querySelector('#testInput-year')!;
+
+ expect(yearInput).not.toHaveFocus();
+
+ fireEvent.change(monthInput, { target: { value: inputValue } });
+
+ if (yearFocusExpected) {
+ expect(yearInput).toHaveFocus();
+ } else {
+ expect(yearInput).not.toHaveFocus();
+ }
+ },
+ );
+
+ it('Invokes the provided onChange function prop if provided', () => {
+ let onChangeParam: DateInputChangeEvent | null = null;
+ const onChange = jest.fn().mockImplementation((val) => (onChangeParam = val));
+
+ const { container } = render( );
+
+ const dayInput = container.querySelector('#testInput-day')!;
+ const monthInput = container.querySelector('#testInput-month')!;
+ const yearInput = container.querySelector('#testInput-year')!;
+
+ fireEvent.change(dayInput, { target: { value: '21' } });
+
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChangeParam!.currentTarget!.value).toEqual({
+ day: '21',
+ month: '',
+ year: '',
+ });
+
+ fireEvent.change(monthInput, { target: { value: '03' } });
+
+ expect(onChange).toHaveBeenCalledTimes(2);
+ expect(onChangeParam!.currentTarget!.value).toEqual({
+ day: '21',
+ month: '03',
+ year: '',
+ });
+
+ fireEvent.change(yearInput, { target: { value: '2024' } });
+
+ expect(onChange).toHaveBeenCalledTimes(3);
+ expect(onChangeParam!.currentTarget!.value).toEqual({
+ day: '21',
+ month: '03',
+ year: '2024',
+ });
+ });
+
+ it('Renders the specified children instead of date fields if provided', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.querySelector('#testInput-day')).toBeNull();
+ expect(container.querySelector('#testInput-month')).toBeNull();
+ expect(container.querySelector('#testInput-year')).toBeNull();
+
+ expect(container.querySelector('#testDiv')).not.toBeNull();
+ });
+});
diff --git a/src/components/form-elements/date-input/__tests__/__snapshots__/DateInput.test.tsx.snap b/src/components/form-elements/date-input/__tests__/__snapshots__/DateInput.test.tsx.snap
new file mode 100644
index 00000000..a93db7f9
--- /dev/null
+++ b/src/components/form-elements/date-input/__tests__/__snapshots__/DateInput.test.tsx.snap
@@ -0,0 +1,90 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DateInput matches snapshot 1`] = `
+
+`;
diff --git a/src/components/date-input/components/IndividualDateInputs.tsx b/src/components/form-elements/date-input/components/IndividualDateInputs.tsx
similarity index 80%
rename from src/components/date-input/components/IndividualDateInputs.tsx
rename to src/components/form-elements/date-input/components/IndividualDateInputs.tsx
index 75c0257a..7fd2e4b9 100644
--- a/src/components/date-input/components/IndividualDateInputs.tsx
+++ b/src/components/form-elements/date-input/components/IndividualDateInputs.tsx
@@ -1,4 +1,5 @@
-import React, { HTMLProps, useContext, ChangeEvent } from 'react';
+'use client';
+import React, { FC, HTMLProps, useContext, ChangeEvent } from 'react';
import classNames from 'classnames';
import Label, { LabelProps } from '../../label/Label';
import DateInputContext, { IDateInputContext } from '../DateInputContext';
@@ -16,7 +17,7 @@ const labels: Record<'day' | 'month' | 'year', string> = {
year: 'Year',
};
-const IndividualDateInput: React.FC = ({
+const IndividualDateInput: FC = ({
label,
labelProps,
inputType,
@@ -28,6 +29,9 @@ const IndividualDateInput: React.FC = ({
error,
value,
defaultValue,
+ pattern = '[0-9]*',
+ inputMode = 'numeric',
+ type = 'text',
...rest
}) => {
const {
@@ -45,7 +49,8 @@ const IndividualDateInput: React.FC = ({
const inputID = id || `${ctxId}-${inputType}`;
const inputName = name || `${ctxName}-${inputType}`;
const inputValue = value !== undefined ? value : ctxValue?.[inputType];
- const inputDefaultValue = defaultValue !== undefined ? defaultValue : ctxDefaultValue?.[inputType];
+ const inputDefaultValue =
+ defaultValue !== undefined ? defaultValue : ctxDefaultValue?.[inputType];
const handleChange = (e: ChangeEvent) => {
e.persist();
@@ -86,6 +91,9 @@ const IndividualDateInput: React.FC = ({
name={inputName}
onChange={handleChange}
ref={refCallback}
+ pattern={pattern}
+ inputMode={inputMode}
+ type={type}
{...rest}
/>
@@ -93,20 +101,14 @@ const IndividualDateInput: React.FC = ({
);
};
-IndividualDateInput.defaultProps = {
- pattern: '[0-9]*',
- inputMode: 'numeric',
- type: 'text',
-};
-
-export const DayInput: React.FC> = (props) => (
+export const DayInput: FC