diff --git a/docs/upgrade-to-4.0.md b/docs/upgrade-to-4.0.md
index 0672d9fb..5a36dc21 100644
--- a/docs/upgrade-to-4.0.md
+++ b/docs/upgrade-to-4.0.md
@@ -81,6 +81,42 @@ Should become this:
It is now required that a `js-enabled` class is added to a parent element if JavaScript is enabled (we suggest using `body`). E.g. `
`.
This is to facilitate a differentiation in styling to the header depending on whether JavaScript is enabled or disabled.
+### CareCard is now a variant of Card
+
+`CareCard` has been removed as a standalone component and the `Card` component has been refactored to provide this functionality. This is achieved via the `cardType` prop.
+
+Notice also that the `immediate` type of card is unsupported - this has been replaced with `emergency`.
+
+Before, using the `CareCard`, you may have markup that looks like this:
+
+```
+
+ Call 999 if:
+
+ Your care card contents
+
+
+```
+
+Now, it should look like this:
+
+```
+
+ Call 999 if:
+
+ Your care card contents
+
+
+```
+
+### NavAZ and ListPanel
+
+Due to these components being [removed from the nhsuk-frontend library](https://github.com/nhsuk/nhsuk-frontend/blob/main/CHANGELOG.md#600---29-november-2021), refactoring has taken place to ensure they still adhere to the NHS digital service manual.
+
+The `NavAZ` component is still available, however the `ListPanel` component has now been renamed to `Panel`. To combine multiple panels you would simply render them as siblings - there is no need to wrap them in a list.
+
+An additional storybook story has been added to give a full example of an A-Z page, combining both of these components. This helps to bring this library into alignment with the [NHS digital service manual listing](https://service-manual.nhs.uk/design-system/patterns/a-to-z-page) for this pattern.
+
## New features
### Text input prefixes + suffixes
@@ -162,7 +198,7 @@ More information can be found in the [NHS digital service manual](https://servic
Usage:
```
-
+
Primary card heading
@@ -180,7 +216,7 @@ More information can be found in the [NHS digital service manual](https://servic
Usage:
```
-
+
Secondary card heading
diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts
index 07571000..76f62a08 100644
--- a/src/__tests__/index.test.ts
+++ b/src/__tests__/index.test.ts
@@ -43,10 +43,10 @@ describe('Index', () => {
'InsetText',
'Label',
'LedeText',
- 'ListPanel',
'MinusIcon',
'NavAZ',
'Pagination',
+ 'Panel',
'PlusIcon',
'Radios',
'ReadingWidth',
diff --git a/src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx b/src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx
index e29653a4..a6773583 100644
--- a/src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx
+++ b/src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx
@@ -1,7 +1,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import CharacterCount, { CharacterCountType } from '../CharacterCount';
-import { Label, HintText, Textarea } from '../../../..';
+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', () => {
diff --git a/src/components/list-panel/ListPanel.tsx b/src/components/list-panel/ListPanel.tsx
deleted file mode 100644
index 58f8de35..00000000
--- a/src/components/list-panel/ListPanel.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React, { HTMLProps } from 'react';
-import classNames from 'classnames';
-import type { AsElementLink } from '@util/types/LinkTypes';
-import { ArrowRight } from '@components/icons';
-
-interface PanelProps extends HTMLProps {
- labelProps?: HTMLProps;
- backToTop?: boolean;
- backToTopButtonText?: string;
- backToTopLink?: string;
- noResults?: boolean;
-}
-
-const Panel: React.FC = ({
- className,
- children,
- noResults,
- label,
- labelProps,
- backToTop,
- backToTopLink,
- backToTopButtonText,
- ...rest
-}) => (
-
-
- {label ? (
-
- {label}
-
- ) : null}
- {noResults ? (
-
- ) : (
-
- )}
- {backToTop ? (
-
- ) : null}
-
-
-);
-
-const PanelItem: React.FC> = ({ className, ...rest }) => (
-
-);
-
-const PanelLinkItem: React.FC> = ({
- className,
- asElement: Component = 'a',
- ...rest
-}) => (
-
-
-
-);
-
-interface ListPanelProps extends HTMLProps {
- type?: 'a' | 'i' | '1' | 'A' | 'I' | undefined;
-}
-
-interface ListPanel extends React.FC {
- LinkItem: React.FC>;
- Item: React.FC>;
- Panel: React.FC;
-}
-
-const ListPanel: ListPanel = ({ className, children, ...rest }) => (
-
- {children}
-
-);
-
-ListPanel.LinkItem = PanelLinkItem;
-ListPanel.Item = PanelItem;
-ListPanel.Panel = Panel;
-
-export default ListPanel;
diff --git a/src/components/list-panel/__tests__/ListPanel.test.tsx b/src/components/list-panel/__tests__/ListPanel.test.tsx
deleted file mode 100644
index dc984c71..00000000
--- a/src/components/list-panel/__tests__/ListPanel.test.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { render } from '@testing-library/react';
-import ListPanel from '../';
-
-describe('ListPanel', () => {
- it('matches snapshot', () => {
- const { container } = render();
-
- expect(container).toMatchSnapshot('ListPanel');
- });
-
- it('renders a list', () => {
- const { container } = render();
-
- expect(container.querySelector('.nhsuk-list')).toBeTruthy();
- });
-
- describe('ListPanel.Panel', () => {
- it('matches snapshot', () => {
- const { container } = render();
-
- expect(container).toMatchSnapshot('ListPanel.Panel');
- });
-
- it('renders label', () => {
- const { container } = render();
- const label = container.querySelector('.nhsuk-list-panel__label');
-
- expect(label).toBeTruthy();
- expect(label?.textContent).toBe('Label');
- expect(
- container.querySelector('.nhsuk-list-panel__list.nhsuk-list-panel__list--with-label'),
- ).toBeTruthy();
- });
-
- it('renders back to top button', () => {
- const { container } = render();
-
- expect(container.querySelector('.nhsuk-back-to-top')).toBeTruthy();
- });
-
- it('renders back to top button with custom text', () => {
- const { container } = render();
-
- expect(container.querySelector('.nhsuk-back-to-top__link')?.textContent).toBe('Custom');
- });
- });
-
- describe('ListPanel.Item', () => {
- it('matches snapshot', () => {
- const { container } = render();
-
- expect(container).toMatchSnapshot('ListPanel.Item');
- });
- });
-
- describe('ListPanel.LinkItem', () => {
- it('matches snapshot', () => {
- const { container } = render();
-
- expect(container).toMatchSnapshot('ListPanel.LinkItem');
- });
- });
-});
diff --git a/src/components/list-panel/__tests__/__snapshots__/ListPanel.test.tsx.snap b/src/components/list-panel/__tests__/__snapshots__/ListPanel.test.tsx.snap
deleted file mode 100644
index edebb7d1..00000000
--- a/src/components/list-panel/__tests__/__snapshots__/ListPanel.test.tsx.snap
+++ /dev/null
@@ -1,43 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ListPanel ListPanel.Item matches snapshot: ListPanel.Item 1`] = `
-
-
-
-`;
-
-exports[`ListPanel ListPanel.LinkItem matches snapshot: ListPanel.LinkItem 1`] = `
-
-`;
-
-exports[`ListPanel ListPanel.Panel matches snapshot: ListPanel.Panel 1`] = `
-
-`;
-
-exports[`ListPanel matches snapshot: ListPanel 1`] = `
-
-`;
diff --git a/src/components/list-panel/index.ts b/src/components/list-panel/index.ts
deleted file mode 100644
index 34adaa08..00000000
--- a/src/components/list-panel/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import ListPanel from './ListPanel';
-
-export default ListPanel;
diff --git a/src/components/navigation/card/Card.tsx b/src/components/navigation/card/Card.tsx
index 309b9463..b9a9b31d 100644
--- a/src/components/navigation/card/Card.tsx
+++ b/src/components/navigation/card/Card.tsx
@@ -9,6 +9,7 @@ import CardHeading from './components/CardHeading';
import CardGroup from './components/CardGroup';
import CardGroupItem from './components/CardGroupItem';
import { CardType } from '@util/types/NHSUKTypes';
+import { cardTypeIsCareCard } from '@util/types/TypeGuards';
interface CardProps extends HTMLProps {
clickable?: boolean;
@@ -25,26 +26,35 @@ interface ICard extends React.FC {
GroupItem: typeof CardGroupItem;
}
-const Card: ICard = ({ className, clickable, children, cardType, ...rest }) => (
-
-
- {children}
-
-
-);
+const Card: ICard = ({ className, clickable, children, cardType, ...rest }) => {
+ let cardClassNames = classNames(
+ 'nhsuk-card',
+ { 'nhsuk-card--clickable': clickable },
+ { 'nhsuk-card--feature': cardType === 'feature' },
+ { 'nhsuk-card--secondary': cardType === 'secondary' },
+ className,
+ );
+
+ if (cardTypeIsCareCard(cardType)) {
+ cardClassNames = classNames(
+ cardClassNames,
+ 'nhsuk-card--care',
+ `nhsuk-card--care--${cardType}`,
+ );
+ }
+
+ return (
+
+
+ {children}
+
+
+ );
+};
Card.Heading = CardHeading;
Card.Description = CardDescription;
diff --git a/src/components/navigation/card/__tests__/Card.test.tsx b/src/components/navigation/card/__tests__/Card.test.tsx
index 05b8851d..2758b384 100644
--- a/src/components/navigation/card/__tests__/Card.test.tsx
+++ b/src/components/navigation/card/__tests__/Card.test.tsx
@@ -111,6 +111,144 @@ describe('Card', () => {
).toBeTruthy();
});
+ describe('Care card variant', () => {
+ describe('non-urgent', () => {
+ it('matches the snapshot', () => {
+ const { container } = render(
+
+ Non urgent heading
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+ it('adds classes to card', () => {
+ const { container } = render(
+
+ Non urgent heading
+ ,
+ );
+
+ expect(container.querySelector('div.nhsuk-card.nhsuk-card.nhsuk-card--care')).toBeTruthy();
+ expect(
+ container.querySelector('div.nhsuk-card.nhsuk-card.nhsuk-card--care--non-urgent'),
+ ).toBeTruthy();
+ });
+
+ it('renders the heading with the expected hidden text', () => {
+ const { container } = render(
+
+ Non urgent heading
+ ,
+ );
+
+ const headingContainer = container.querySelector('.nhsuk-card--care__heading-container');
+
+ expect(headingContainer?.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual(
+ 'Non-urgent advice: ',
+ );
+ });
+ });
+
+ describe('urgent', () => {
+ it('matches the snapshot', () => {
+ const { container } = render(
+
+ Urgent heading
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+ it('adds classes to card', () => {
+ const { container } = render(
+
+ Urgent heading
+ ,
+ );
+
+ expect(container.querySelector('div.nhsuk-card.nhsuk-card.nhsuk-card--care')).toBeTruthy();
+ expect(
+ container.querySelector('div.nhsuk-card.nhsuk-card.nhsuk-card--care--urgent'),
+ ).toBeTruthy();
+ });
+
+ it('renders the heading with the expected hidden text', () => {
+ const { container } = render(
+
+ Urgent heading
+ ,
+ );
+
+ const headingContainer = container.querySelector('.nhsuk-card--care__heading-container');
+
+ expect(headingContainer?.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual(
+ 'Urgent advice: ',
+ );
+ });
+ });
+
+ describe('emergency', () => {
+ it('matches the snapshot', () => {
+ const { container } = render(
+
+ Emergency heading
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+ it('adds classes to card', () => {
+ const { container } = render(
+
+ Emergency heading
+ ,
+ );
+
+ expect(container.querySelector('div.nhsuk-card.nhsuk-card.nhsuk-card--care')).toBeTruthy();
+ expect(
+ container.querySelector('div.nhsuk-card.nhsuk-card.nhsuk-card--care--emergency'),
+ ).toBeTruthy();
+ });
+
+ it('renders the heading with the expected hidden text', () => {
+ const { container } = render(
+
+ Emergency heading
+ ,
+ );
+
+ const headingContainer = container.querySelector('.nhsuk-card--care__heading-container');
+
+ expect(headingContainer?.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual(
+ 'Immediate action required: ',
+ );
+ });
+ });
+
+ describe('hidden text', () => {
+ it('renders without hidden text', () => {
+ const { container } = render(
+
+ Heading
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-u-visually-hidden')).toBeFalsy();
+ });
+
+ it('renders with hidden text', () => {
+ const { container } = render(
+
+ Heading
+ ,
+ );
+
+ expect(container.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual('Custom');
+ });
+ });
+ });
+
describe('Card.Group', () => {
it('matches snapshot', () => {
const { container } = render(
diff --git a/src/components/navigation/card/__tests__/__snapshots__/Card.test.tsx.snap b/src/components/navigation/card/__tests__/__snapshots__/Card.test.tsx.snap
index 8b78a8a1..e45e8b15 100644
--- a/src/components/navigation/card/__tests__/__snapshots__/Card.test.tsx.snap
+++ b/src/components/navigation/card/__tests__/__snapshots__/Card.test.tsx.snap
@@ -53,6 +53,99 @@ exports[`Card Card.Group matches snapshot 1`] = `
`;
+exports[`Card Care card variant emergency matches the snapshot 1`] = `
+
+
+
+
+
+
+ Immediate action required:
+
+ Emergency heading
+
+
+
+
+
+
+`;
+
+exports[`Card Care card variant non-urgent matches the snapshot 1`] = `
+
+
+
+
+
+
+ Non-urgent advice:
+
+ Non urgent heading
+
+
+
+
+
+
+`;
+
+exports[`Card Care card variant urgent matches the snapshot 1`] = `
+
+
+
+
+
+
+ Urgent advice:
+
+ Urgent heading
+
+
+
+
+
+
+`;
+
exports[`Card matches snapshot 1`] = `
{
headingLevel?: HeadingLevelType;
+ visuallyHiddenText?: false | string;
}
-const CardHeading: React.FC
= ({ className, headingLevel = 'h2', ...rest }) => {
+const genHiddenText = (cardType: CareCardType): string => {
+ switch (cardType) {
+ case 'non-urgent':
+ return 'Non-urgent advice: ';
+ case 'urgent':
+ return 'Urgent advice: ';
+ case 'emergency':
+ return 'Immediate action required: ';
+ default:
+ return '';
+ }
+};
+
+const CareHeading: React.FC = ({
+ className,
+ children,
+ visuallyHiddenText,
+ careType,
+ headingLevel = 'h2',
+ role = 'text',
+ ...rest
+}) => {
+ return (
+
+
+
+ {visuallyHiddenText !== false ? (
+
+ {visuallyHiddenText || genHiddenText(careType)}
+
+ ) : null}
+ {children}
+
+
+
+
+ );
+};
+
+const CardHeading: React.FC = (props) => {
const { cardType } = useContext(CardContext);
+
+ if (cardTypeIsCareCard(cardType)) {
+ return ;
+ }
+
+ const { className, headingLevel = 'h2', ...rest } = props;
+
return (
{
- type: CareCardType;
-}
-
-const CareCardContext = createContext('non-urgent');
-
-const genHiddenText = (cardType: CareCardType): string => {
- switch (cardType) {
- case 'non-urgent':
- return 'Non-urgent advice: ';
- case 'urgent':
- return 'Urgent advice: ';
- case 'immediate':
- return 'Immediate action required: ';
- default:
- return '';
- }
-};
-
-const CareCardContent: React.FC> = ({ className, ...rest }) => (
-
-);
-
-interface CareCardHeadingProps extends HTMLProps {
- visuallyHiddenText?: false | string;
- headingLevel?: HeadingLevelType;
-}
-
-const CareCardHeading: React.FC = ({
- className,
- children,
- visuallyHiddenText,
- headingLevel,
- role = 'text',
- ...rest
-}) => {
- const cardType = useContext(CareCardContext);
- return (
-
-
-
- {visuallyHiddenText !== false ? (
-
- {visuallyHiddenText || genHiddenText(cardType)}
-
- ) : null}
- {children}
-
-
-
-
- );
-};
-
-interface CareCard extends React.FC {
- Content: React.FC>;
- Heading: React.FC;
-}
-
-const CareCard: CareCard = ({ className, type, children, ...rest }) => (
-
- {children}
-
-);
-
-CareCard.Content = CareCardContent;
-CareCard.Heading = CareCardHeading;
-
-export default CareCard;
diff --git a/src/patterns/care-card/__tests__/CareCard.tests.tsx b/src/patterns/care-card/__tests__/CareCard.tests.tsx
deleted file mode 100644
index 6a33dcb1..00000000
--- a/src/patterns/care-card/__tests__/CareCard.tests.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import React from 'react';
-import { render } from '@testing-library/react';
-import CareCard from '../';
-
-describe('CareCard', () => {
- it('renders content', () => {
- const { container } = render(
-
- Test Content
- ,
- );
-
- expect(container.querySelector('.nhsuk-care-card__content')?.textContent).toEqual(
- 'Test Content',
- );
- });
-
- describe('urgent', () => {
- it('matches snapshot', () => {
- const { container } = render();
-
- expect(container).toMatchSnapshot('BaseCareCardUrgent');
- });
-
- it('renders with correct classNames', () => {
- const { container } = render();
-
- expect(container.querySelector('.nhsuk-care-card--urgent')).toBeTruthy();
- });
-
- it('generates the correct hidden text', () => {
- const { container } = render(
-
- Heading
- ,
- );
-
- expect(container.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual(
- 'Urgent advice: ',
- );
- });
- });
-
- describe('immediate', () => {
- it('matches snapshot', () => {
- const { container } = render();
-
- expect(container).toMatchSnapshot('BaseCareCardImmediate');
- });
-
- it('renders with correct classNames', () => {
- const { container } = render();
-
- expect(container.querySelector('.nhsuk-care-card--immediate')).toBeTruthy();
- });
-
- it('generates the correct hidden text', () => {
- const { container } = render(
-
- Heading
- ,
- );
-
- expect(container.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual(
- 'Immediate action required: ',
- );
- });
- });
-
- describe('non-urgent', () => {
- it('matches snapshot', () => {
- const { container } = render();
-
- expect(container).toMatchSnapshot('BaseCareCardNonUrgent');
- });
-
- it('renders with correct classNames', () => {
- const { container } = render();
-
- expect(container.querySelector('.nhsuk-care-card--non-urgent')).toBeTruthy();
- });
-
- it('generates the correct hidden text', () => {
- const { container } = render(
-
- Heading
- ,
- );
-
- expect(container.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual(
- 'Non-urgent advice: ',
- );
- });
- });
-
- describe('hidden text', () => {
- it('renders without hidden text', () => {
- const { container } = render(
-
- Heading
- ,
- );
-
- expect(container.querySelector('.nhsuk-u-visually-hidden')).toBeFalsy();
- });
-
- it('renders with hidden text', () => {
- const { container } = render(
-
- Heading
- ,
- );
-
- expect(container.querySelector('.nhsuk-u-visually-hidden')?.textContent).toEqual('Custom');
- });
- });
-});
diff --git a/src/patterns/care-card/__tests__/__snapshots__/CareCard.tests.tsx.snap b/src/patterns/care-card/__tests__/__snapshots__/CareCard.tests.tsx.snap
deleted file mode 100644
index c83ff56c..00000000
--- a/src/patterns/care-card/__tests__/__snapshots__/CareCard.tests.tsx.snap
+++ /dev/null
@@ -1,25 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`CareCard immediate matches snapshot: BaseCareCardImmediate 1`] = `
-
-`;
-
-exports[`CareCard non-urgent matches snapshot: BaseCareCardNonUrgent 1`] = `
-
-`;
-
-exports[`CareCard urgent matches snapshot: BaseCareCardUrgent 1`] = `
-
-`;
diff --git a/src/patterns/care-card/index.ts b/src/patterns/care-card/index.ts
deleted file mode 100644
index 2e950031..00000000
--- a/src/patterns/care-card/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import CareCard from './CareCard';
-
-export default CareCard;
diff --git a/src/patterns/nav-a-z/NavAZ.tsx b/src/patterns/nav-a-z/NavAZ.tsx
index 9f2efe6c..125a99bf 100644
--- a/src/patterns/nav-a-z/NavAZ.tsx
+++ b/src/patterns/nav-a-z/NavAZ.tsx
@@ -88,14 +88,14 @@ const LinkItem: React.FC> = ({
);
-// TODO - check disabled here - it probably doesn't work
const DisabledItem: React.FC> = ({ className, ...rest }) => (
{
render();
expect(
- screen.queryByText('A')?.classList.contains('nhsuk-u-display-block--disabled'),
+ screen.queryByText('A')?.classList.contains('nhsuk-u-secondary-text-color'),
).toBeTruthy();
expect(
- screen.queryByText('B')?.classList.contains('nhsuk-u-display-block--disabled'),
+ screen.queryByText('B')?.classList.contains('nhsuk-u-secondary-text-color'),
).toBeTruthy();
- expect(
- screen.queryByText('C')?.classList.contains('nhsuk-u-display-block--disabled'),
- ).toBeFalsy();
+ expect(screen.queryByText('C')?.classList.contains('nhsuk-u-secondary-text-color')).toBeFalsy();
});
it('passes through children', () => {
diff --git a/src/patterns/panel/Panel.tsx b/src/patterns/panel/Panel.tsx
new file mode 100644
index 00000000..91739cfb
--- /dev/null
+++ b/src/patterns/panel/Panel.tsx
@@ -0,0 +1,68 @@
+import React, { HTMLProps } from 'react';
+import classNames from 'classnames';
+import type { AsElementLink } from '@util/types/LinkTypes';
+import Card from '@components/navigation/card';
+
+interface PanelProps extends HTMLProps {
+ labelProps?: HTMLProps;
+ backToTop?: boolean;
+ backToTopButtonText?: string;
+ backToTopLink?: string;
+}
+
+interface Panel extends React.FC {
+ LinkItem: React.FC>;
+ Item: React.FC>;
+}
+
+const Panel: Panel = ({
+ children,
+ label,
+ labelProps,
+ backToTop,
+ backToTopLink,
+ backToTopButtonText,
+}) => (
+ <>
+
+
+ {label ? (
+
+ {label}
+
+ ) : null}
+
+
+
+
+ {backToTop ? (
+
+ ) : null}
+ >
+);
+
+const PanelItem: React.FC> = ({ className, ...rest }) => (
+
+);
+
+const PanelLinkItem: React.FC> = ({
+ className,
+ asElement: Component = 'a',
+ ...rest
+}) => (
+
+
+
+);
+
+Panel.LinkItem = PanelLinkItem;
+Panel.Item = PanelItem;
+
+export default Panel;
diff --git a/src/patterns/panel/__tests__/Panel.test.tsx b/src/patterns/panel/__tests__/Panel.test.tsx
new file mode 100644
index 00000000..de87e855
--- /dev/null
+++ b/src/patterns/panel/__tests__/Panel.test.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import Panel from '..';
+
+describe('Panel', () => {
+ it('matches snapshot', () => {
+ const { container } = render();
+
+ expect(container).toMatchSnapshot('ListPanel');
+ });
+
+ it('renders label', () => {
+ const { container } = render();
+ const label = container.querySelector('.test-label');
+
+ expect(label).toBeTruthy();
+ expect(label?.textContent).toBe('Label');
+ });
+
+ it('renders back to top button', () => {
+ const { container } = render();
+
+ expect(container.querySelector('.nhsuk-back-to-top')).toBeTruthy();
+ });
+
+ it('does not render back to top button', () => {
+ const { container } = render();
+
+ expect(container.querySelector('.nhsuk-back-to-top')).toBeNull();
+ });
+
+ it('renders back to top button with custom text', () => {
+ const { container } = render();
+
+ expect(container.querySelector('.nhsuk-back-to-top__link')?.textContent).toBe('Custom');
+ });
+
+ describe('ListPanel.Item', () => {
+ it('matches snapshot', () => {
+ const { container } = render();
+
+ expect(container).toMatchSnapshot('ListPanel.Item');
+ });
+ });
+
+ describe('ListPanel.LinkItem', () => {
+ it('matches snapshot', () => {
+ const { container } = render();
+
+ expect(container).toMatchSnapshot('ListPanel.LinkItem');
+ });
+ });
+});
diff --git a/src/patterns/panel/__tests__/__snapshots__/Panel.test.tsx.snap b/src/patterns/panel/__tests__/__snapshots__/Panel.test.tsx.snap
new file mode 100644
index 00000000..e9446df6
--- /dev/null
+++ b/src/patterns/panel/__tests__/__snapshots__/Panel.test.tsx.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Panel ListPanel.Item matches snapshot: ListPanel.Item 1`] = `
+
+
+
+`;
+
+exports[`Panel ListPanel.LinkItem matches snapshot: ListPanel.LinkItem 1`] = `
+
+`;
+
+exports[`Panel matches snapshot: ListPanel 1`] = `
+
+`;
diff --git a/src/patterns/panel/index.ts b/src/patterns/panel/index.ts
new file mode 100644
index 00000000..ac8432fe
--- /dev/null
+++ b/src/patterns/panel/index.ts
@@ -0,0 +1,3 @@
+import ListPanel from './Panel';
+
+export default ListPanel;
diff --git a/src/util/types/NHSUKTypes.ts b/src/util/types/NHSUKTypes.ts
index e2a8a394..fe75353e 100644
--- a/src/util/types/NHSUKTypes.ts
+++ b/src/util/types/NHSUKTypes.ts
@@ -2,9 +2,9 @@ export type NHSUKSize = 's' | 'm' | 'l' | 'xl';
export type InputWidth = '2' | '3' | '4' | '5' | '10' | '20' | '30' | 2 | 3 | 4 | 5 | 10 | 20 | 30;
-export type CareCardType = 'non-urgent' | 'urgent' | 'immediate';
+export type CareCardType = 'non-urgent' | 'urgent' | 'emergency';
-export type CardType = 'feature' | 'primary' | 'secondary';
+export type CardType = 'feature' | 'primary' | 'secondary' | CareCardType;
export type ColWidth =
| 'full'
diff --git a/src/util/types/TypeGuards.ts b/src/util/types/TypeGuards.ts
index df1f658a..f1566034 100644
--- a/src/util/types/TypeGuards.ts
+++ b/src/util/types/TypeGuards.ts
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ReactElement, JSXElementConstructor, ReactNode, ReactPortal } from 'react';
+import { CardType, CareCardType } from './NHSUKTypes';
/**
* Assert that a child item is of the given component type.
@@ -23,3 +24,9 @@ export const childIsOfComponentType = (
typeof child === 'object' &&
'type' in child &&
child.type === component;
+
+/**
+ * Check whether the given card type is that of a care card.
+ */
+export const cardTypeIsCareCard = (cardType: CardType | undefined): cardType is CareCardType =>
+ cardType === 'non-urgent' || cardType === 'urgent' || cardType === 'emergency';
diff --git a/stories/Components/ListPanel.stories.tsx b/stories/Components/ListPanel.stories.tsx
deleted file mode 100644
index a3a11eb4..00000000
--- a/stories/Components/ListPanel.stories.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import { ListPanel } from '../../src';
-import { Meta, StoryObj } from '@storybook/react';
-
-const meta: Meta = {
- title: 'Components/ListPanel',
- component: ListPanel,
-};
-export default meta;
-type Story = StoryObj;
-
-ListPanel.Panel.displayName = 'ListPanel.Panel';
-ListPanel.LinkItem.displayName = 'ListPanel.LinkItem';
-
-export const Standard: Story = {
- argTypes: {
- type: { table: { disable: true } },
- },
- render: (args) => (
-
-
- AAA
-
- Abdominal aortic aneurysm
-
- Abscess
-
-
- There are currently no conditions listed
-
-
- Chest pain
- Cold sore
-
-
- Dandruff
- Dementia
- Dental pain
-
-
- ),
-};
diff --git a/stories/Navigation/Card.stories.tsx b/stories/Navigation/Card.stories.tsx
index 9ee3b3af..3c23d2ef 100644
--- a/stories/Navigation/Card.stories.tsx
+++ b/stories/Navigation/Card.stories.tsx
@@ -168,3 +168,107 @@ export const CardGroup: Story = {
),
};
+
+export const NonUrgentCareCard: Story = {
+ args: { cardType: 'non-urgent' },
+ render: (args) => (
+
+ Speak to a GP if:
+
+
+ - you're not sure it's chickenpox
+ - the skin around the blisters is red, hot or painful (signs of infection)
+ -
+ your child is dehydrated
+
+ - you're concerned about your child or they get worse
+
+
+ Tell the receptionist you think it's chickenpox before going in. They may recommend a
+ special appointment time if other patients are at risk.
+
+
+
+ ),
+};
+
+export const UrgentCareCard: Story = {
+ args: { cardType: 'urgent' },
+ render: (args) => (
+
+ Ask for an urgent GP appointment if:
+
+
+ - you're an adult and have chickenpox
+ -
+ you're pregnant and haven't had chickenpox before and you've been near
+ someone with it
+
+ -
+ you have a weakened immune system and you've been near someone with chickenpox
+
+ - you think your newborn baby has chickenpox
+
+
+ In these situations, your GP can prescribe medicine to prevent complications. You need to
+ take it within 24 hours of the spots coming out.
+
+
+
+ ),
+};
+
+export const EmergencyCareCard: Story = {
+ args: { cardType: 'emergency' },
+ render: (args) => (
+
+ Call 999 or go to A&E now if:
+
+
+ - you or someone you know needs immediate help
+ - you have seriously harmed yourself - for example, by taking a drug overdose
+
+ A mental health emergency should be taken as seriously as a medical emergency.
+
+ Find your nearest A&E
+
+
+
+ ),
+};
+
+/**
+ * By default, CareCard components prepend hidden text before the title. These are:
+ *
+ * - ("non-urgent") Non-urgent advice:
+ * - ("urgent") Urgent advice:
+ * - ("immediate") Immediate action required:
+ *
+ * If you wish to disable this behaviour, pass the prop `visuallyHiddenText={false}` to the `CareCard.Heading` component or specify your own visually hidden text by using `visuallyHiddenText="Custom"`.
+ *
+ * You can change the heading type (i.e. `h1`, `h2` and so on) of the title by passing the prop `headingLevel=""` to the `CareCard.Heading`.
+ */
+export const WithCustomVisuallyHiddenText: Story = {
+ args: { cardType: 'non-urgent' },
+ render: (args) => (
+
+
+ Speak to a GP if:
+
+
+
+ - you're not sure it's chickenpox
+ - the skin around the blisters is red, hot or painful (signs of infection)
+ -
+ your child is dehydrated
+
+ - you're concerned about your child or they get worse
+
+
+ Tell the receptionist you think it's chickenpox before going in. They may recommend a
+ special appointment time if other patients are at risk.
+
+
+
+ ),
+};
diff --git a/stories/Patterns/CareCard.stories.tsx b/stories/Patterns/CareCard.stories.tsx
deleted file mode 100644
index cb0e39a7..00000000
--- a/stories/Patterns/CareCard.stories.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import React from 'react';
-import CareCard from '../../src/patterns/care-card/CareCard';
-import type { Meta, StoryObj } from '@storybook/react';
-
-/**
- * This component can be found in the `nhsuk-frontend` repository here.
- *
- * ## Implementation Notes
- *
- * By default, CareCard components prepend hidden text before the title. These are:
- *
- * - ("non-urgent") Non-urgent advice:
- * - ("urgent") Urgent advice:
- * - ("immediate") Immediate action required:
- *
- * If you wish to disable this behaviour, pass the prop `visuallyHiddenText={false}` to the `CareCard.Heading` component or specify your own visually hidden text by using `visuallyHiddenText="Custom"`.
- *
- * You can change the heading type (i.e. `h1`, `h2` and so on) of the title by passing the prop `headingLevel=""` to the `CareCard.Heading`.
- *
- * ## Usage
- *
- *
- * ### Standard
- *
- * ```jsx
- * import { CareCard } from "nhsuk-react-components";
- *
- * const Element = () => {
- * return (
- *
- * Speak to a GP if:
- *
- *
- * - you're not sure it's chickenpox
- * - the skin around the blisters is red, hot or painful (signs of infection)
- * - your child is dehydrated
- * - you're concerned about your child or they get worse
- *
- * Tell the receptionist you think it's chickenpox before going in. They may recommend a special appointment time if other patients are at risk.
- *
- *
- * );
- * }
- * ```
- */
-const meta: Meta = {
- title: 'Components/CareCard',
- component: CareCard,
-};
-export default meta;
-
-CareCard.Heading.displayName = 'CareCard.Heading';
-CareCard.Content.displayName = 'CareCard.Content';
-
-type Story = StoryObj;
-export const NonUrgent: Story = {
- args: { type: 'non-urgent' },
- render: (args): JSX.Element => (
-
- Speak to a GP if:
-
-
- - you're not sure it's chickenpox
- - the skin around the blisters is red, hot or painful (signs of infection)
- -
- your child is dehydrated
-
- - you're concerned about your child or they get worse
-
-
-
- ),
- name: 'Non-Urgent',
-};
-
-export const Urgent: Story = {
- args: { type: 'urgent' },
- render: (args) => (
-
- Ask for an urgent GP appointment if:
-
-
- - you're an adult and have chickenpox
- -
- you're pregnant and haven't had chickenpox before and you've been near
- someone with it
-
- -
- you have a weakened immune system and you've been near someone with chickenpox
-
- - you think your newborn baby has chickenpox
-
-
- In these situations, your GP can prescribe medicine to prevent complications. You need to
- take it within 24 hours of the spots coming out.
-
-
-
- ),
-};
-
-export const Immediate: Story = {
- args: { type: 'immediate' },
- render: (args) => (
-
- Call 999 if you have sudden chest pain that:
-
-
- - spreads to your arms, back, neck or jaw
- - makes your chest feel tight or heavy
- - also started with shortness of breath, sweating and feeling or being sick
-
-
- You could be having a heart attack. Call 999 immediately as you need immediate treatment
- in hospital.
-
-
-
- ),
-};
-
-export const WithoutVisuallyHiddenText: Story = {
- args: {
- type: 'non-urgent',
- },
- render: (args) => (
-
- Speak to a GP if:
-
-
- - you're not sure it's chickenpox
- - the skin around the blisters is red, hot or painful (signs of infection)
- -
- your child is dehydrated
-
- - you're concerned about your child or they get worse
-
-
- Tell the receptionist you think it's chickenpox before going in. They may recommend a
- special appointment time if other patients are at risk.
-
-
-
- ),
-};
-
-export const WithCustomVisuallyHiddenText: Story = {
- args: {
- type: 'non-urgent',
- },
- render: (args) => (
-
- Speak to a GP if:
-
-
- - you're not sure it's chickenpox
- - the skin around the blisters is red, hot or painful (signs of infection)
- -
- your child is dehydrated
-
- - you're concerned about your child or they get worse
-
-
- Tell the receptionist you think it's chickenpox before going in. They may recommend a
- special appointment time if other patients are at risk.
-
-
-
- ),
-};
diff --git a/stories/Patterns/NavAZ.stories.tsx b/stories/Patterns/NavAZ.stories.tsx
index 40e5d084..baf9934f 100644
--- a/stories/Patterns/NavAZ.stories.tsx
+++ b/stories/Patterns/NavAZ.stories.tsx
@@ -2,8 +2,12 @@ import React from 'react';
import { NavAZ } from '../../src';
import { Meta, StoryObj } from '@storybook/react';
+/**
+ * This component is generally used as part of the 'A to Z page' pattern.
+ */
+
const meta: Meta = {
- title: 'Components/NavAZ',
+ title: 'Patterns/NavAZ',
component: NavAZ,
args: {
fullAlphabet: false,
diff --git a/stories/Patterns/PageAZ.stories.tsx b/stories/Patterns/PageAZ.stories.tsx
new file mode 100644
index 00000000..6e6230ca
--- /dev/null
+++ b/stories/Patterns/PageAZ.stories.tsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import { Meta, StoryObj } from '@storybook/react';
+import NavAZ from '@patterns/nav-a-z';
+import HeadingLevel from '@util/HeadingLevel';
+import Panel from '@patterns/panel/Panel';
+import Container from '@components/layout/Container';
+import Row from '@components/layout/Row';
+import Col from '@components/layout/Col';
+
+/**
+ * A to Z is a way of presenting a number of pages alphabetically.
+ *
+ * See the NHS digital service manual: https://service-manual.nhs.uk/design-system/patterns/a-to-z-page
+ */
+
+const meta: Meta = {
+ title: 'Patterns/PageAZ',
+ args: {
+ fullAlphabet: false,
+ removedLetters: [],
+ disabledLetters: [],
+ letters: [],
+ },
+};
+export default meta;
+type Story = StoryObj;
+
+export const Standard: Story = {
+ render: () => (
+
+
+
+
+ Health A to Z
+
+
+ A
+ B
+ C
+ D
+ E
+ F
+ G
+ H
+ I
+ J
+ K
+ L
+ M
+ N
+ O
+ P
+ Q
+ R
+ S
+ T
+ U
+ V
+ W
+ X
+ Y
+ Z
+
+
+
+ AAA
+
+ Abdominal aortic aneurysm
+
+ Abscess
+
+
+
+ There are currently no conditions listed
+
+
+
+ Chest pain
+ Cold sore
+
+
+
+ Dandruff
+ Dementia
+ Toothache
+
+
+
+
+
+ ),
+};
+
+NavAZ.LinkItem.displayName = 'NavAZ.LinkItem';
diff --git a/stories/Patterns/Panel.stories.tsx b/stories/Patterns/Panel.stories.tsx
new file mode 100644
index 00000000..4462efee
--- /dev/null
+++ b/stories/Patterns/Panel.stories.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { Meta, StoryObj } from '@storybook/react';
+import Panel from '@patterns/panel/Panel';
+
+const meta: Meta = {
+ title: 'Components/Panel',
+ component: Panel,
+};
+export default meta;
+type Story = StoryObj;
+
+Panel.LinkItem.displayName = 'Panel.LinkItem';
+
+export const Standard: Story = {
+ argTypes: {
+ type: { table: { disable: true } },
+ },
+ render: () => (
+
+ AAA
+
+ Abdominal aortic aneurysm
+
+ Abscess
+
+ ),
+};