Skip to content

Commit

Permalink
Add new components since nhsuk-frontend v5
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeb-nhs committed Mar 22, 2024
1 parent 03cc471 commit d2b7f4b
Show file tree
Hide file tree
Showing 69 changed files with 2,363 additions and 1,160 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ module.exports = {
jest: true,
},
settings: {
'import/resolver': {
typescript: {},
},
react: {
version: 'detect',
},
Expand Down
8 changes: 8 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { StorybookConfig } from '@storybook/react-vite';
import { mergeConfig } from 'vite';
import tsConfigPaths from 'vite-tsconfig-paths';

const config: StorybookConfig = {
stories: ['../stories/**/*.stories.@(ts|tsx)', '../stories/**/*.mdx'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
Expand All @@ -9,5 +12,10 @@ const config: StorybookConfig = {
docs: {
autodocs: true,
},
viteFinal(config) {
return mergeConfig(config, {
plugins: [tsConfigPaths()],
});
},
};
export default config;
114 changes: 114 additions & 0 deletions docs/upgrade-to-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,117 @@ You can now specify prefixes and/or suffixes for your text inputs. These are exp
```
<TextInput prefix="£" suffix="pounds" />
```

### Exclusive option for checkboxes

Added "None of the above" exclusive behaviour to checkboxes - allowing all checkboxes in a group to be automatically unchecked when the "None of the above" option is checked. To use this feature, a new prop is available on `Checkbox.Box` - set the `exclusive` prop to make that option exclusive, e.g.

```
<Checkboxes id="symptoms" name="symptoms" hint="Select all the symptoms you have.">
<Checkboxes.Box value="sore-throat">Sore throat</Checkboxes.Box>
<Checkboxes.Box value="runny-nose">Runny nose</Checkboxes.Box>
<Checkboxes.Box value="muscle-pain">Muscle or joint pain</Checkboxes.Box>
<Checkboxes.Divider />
<Checkboxes.Box value="none" exclusive>
None
</Checkboxes.Box>
</Checkboxes>
```

### New component - Character Count

See [the Digital Service Manual](https://service-manual.nhs.uk/design-system/components/character-count) for information.
Usage:

```
<CharacterCount
maxLength={150}
countType={CharacterCountType.Characters}
textAreaId="more-details"
>
<Label htmlFor="more-details">Can you provide more detail?</Label>
<Textarea id="more-details" className="nhsuk-js-character-count" name="more-details" rows={5} />
</CharacterCount>
```

### New component - Tabs

See [the Digital Service Manual](https://service-manual.nhs.uk/design-system/components/tabs) for information.
Usage:

```
<Tabs>
<Tabs.Title>Contents</Tabs.Title>
<Tabs.List>
<Tabs.ListItem id="past-day">Past day</Tabs.ListItem>
<Tabs.ListItem id="past-week">Past week</Tabs.ListItem>
<Tabs.ListItem id="past-month">Past month</Tabs.ListItem>
</Tabs.List>
<Tabs.Contents id="past-day">
<div>Past day contents go here</div>
</Tabs.Contents>
<Tabs.Contents id="past-week">
<div>Past week contents go here</div>
</Tabs.Contents>
<Tabs.Contents id="past-month">
<div>Past month contents go here</div>
</Tabs.Contents>
</Tabs>
```

### New card variants

Two new card variants have been added - `primary` and `secondary`.

#### Primary

More information can be found in the [NHS digital service manual](https://service-manual.nhs.uk/design-system/components/card#primary-card-with-chevron)

Usage:

```
<Card clickable primary>
<Card.Content>
<Card.Heading>
<Card.Link href="#">Primary card heading</Card.Link>
</Card.Heading>
<Card.Description>Primary card description</Card.Description>
<ChevronRightCircle />
</Card.Content>
</Card>
```

#### Secondary

More information can be found in the [NHS digital service manual](https://service-manual.nhs.uk/design-system/components/card#secondary-card)

Usage:

```
<Card clickable secondary>
<Card.Content>
<Card.Heading>
<Card.Link href="#">Secondary card heading</Card.Link>
</Card.Heading>
<Card.Description>Secondary card description</Card.Description>
</Card.Content>
</Card>
```

## Notes for maintainers

This version switches to using `yarn` 4.x. This can be enabled and used through running the following commands:

```
# Remove yarn
npm -g remove yarn
# Enable corepack
corepack enable
# Set yarn version
yarn set version stable
```
6 changes: 3 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ const { compilerOptions } = require('./tsconfig.json');

const jestConfig = {
testEnvironment: 'jsdom',
rootDir: './src',
setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
collectCoverageFrom: ['<rootDir>/**/*.{ts,tsx}'],
rootDir: './',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
collectCoverageFrom: ['<rootDir>/src/**/*.{ts,tsx}'],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: '<rootDir>',
}),
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^5.1.3",
Expand All @@ -78,7 +79,8 @@
"storybook": "^7.6.17",
"ts-jest": "^29.1.2",
"typescript": "5.3.3",
"vite": "^4.2.1"
"vite": "^4.2.1",
"vite-tsconfig-paths": "^4.3.2"
},
"dependencies": {
"classnames": "^2.2.6"
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ describe('Index', () => {
'Button',
'ButtonLink',
'Card',
'CharacterCount',
'CharacterCountType',
'Checkboxes',
'ChevronLeftIcon',
'ChevronRightIcon',
Expand Down Expand Up @@ -56,6 +58,7 @@ describe('Index', () => {
'SmallEmdashIcon',
'SummaryList',
'Table',
'Tabs',
'Tag',
'TextInput',
'Textarea',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
'use client';
import React, { HTMLProps, createContext, useContext, ReactNode } from 'react';
import classNames from 'classnames';
import { Tick, Cross } from '../../icons';
import HeadingLevel, { HeadingLevelType } from '../../../util/HeadingLevel';
import { Tick, Cross } from '@components/icons';
import HeadingLevel, { HeadingLevelType } from '@util/HeadingLevel';

type ListType = 'do' | 'dont';

interface DoDontListProps extends HTMLProps<HTMLDivElement> {
interface DoAndDontListProps extends HTMLProps<HTMLDivElement> {
listType: ListType;
heading?: string;
headingLevel?: HeadingLevelType;
}

interface DoDontList extends React.FC<DoDontListProps> {
Item: React.FC<DoDontItemProps>;
interface DoAndDontList extends React.FC<DoAndDontListProps> {
Item: React.FC<DoAndDontItemProps>;
}

const DoDontListContext = createContext<ListType>('do');
const DoAndDontListContext = createContext<ListType>('do');

const DoDontList: DoDontList = ({
const DoAndDontList: DoAndDontList = ({
className,
listType,
children,
Expand All @@ -38,19 +38,24 @@ const DoDontList: DoDontList = ({
{ 'nhsuk-list--cross': listType === 'dont' },
)}
>
<DoDontListContext.Provider value={listType}>{children}</DoDontListContext.Provider>
<DoAndDontListContext.Provider value={listType}>{children}</DoAndDontListContext.Provider>
</ul>
</div>
);
};

interface DoDontItemProps extends HTMLProps<HTMLLIElement> {
interface DoAndDontItemProps extends HTMLProps<HTMLLIElement> {
listItemType?: ListType;
prefixText?: ReactNode;
}

const DoDontItem: React.FC<DoDontItemProps> = ({ prefixText, listItemType, children, ...rest }) => {
const listItem = useContext(DoDontListContext);
const DoAndDontItem: React.FC<DoAndDontItemProps> = ({
prefixText,
listItemType,
children,
...rest
}) => {
const listItem = useContext(DoAndDontListContext);
const defaultPrefix = (listItemType || listItem) === 'do' ? null : 'do not ';
const actualPrefix = prefixText === undefined ? defaultPrefix : prefixText;
return (
Expand All @@ -71,6 +76,6 @@ const DoDontItem: React.FC<DoDontItemProps> = ({ prefixText, listItemType, child
);
};

DoDontList.Item = DoDontItem;
DoAndDontList.Item = DoAndDontItem;

export default DoDontList;
export default DoAndDontList;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';
import classNames from 'classnames';
import React, { HTMLProps, useContext } from 'react';
import useDevWarning from '../../../../util/hooks/UseDevWarning';
import useDevWarning from '@util/hooks/UseDevWarning';
import TableSectionContext, { TableSection } from '../TableSectionContext';

const CellOutsideOfSectionWarning =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { ComponentProps, HTMLProps } from 'react';
import classNames from 'classnames';
import HeadingLevel from '../../../../util/HeadingLevel';
import HeadingLevel from '@util/HeadingLevel';

export interface TablePanelProps extends HTMLProps<HTMLDivElement> {
heading?: string;
Expand Down
73 changes: 73 additions & 0 deletions src/components/content-presentation/tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use client';
import classNames from 'classnames';
import React, { HTMLAttributes, useEffect } from 'react';
import HeadingLevel, { HeadingLevelType } from '@util/HeadingLevel';
import TabsJs from 'nhsuk-frontend/packages/components/tabs/tabs.js';

type TabsProps = HTMLAttributes<HTMLDivElement>;

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: React.FC<TabTitleProps> = ({ children, headingLevel = 'h2' }) => (
<HeadingLevel className="nhsuk-tabs__title" headingLevel={headingLevel}>
{children}
</HeadingLevel>
);

const TabList: React.FC<TabListProps> = ({ children }) => (
<ul className="nhsuk-tabs__list">{children}</ul>
);

const TabListItem: React.FC<TabListItemProps> = ({ id, children }) => (
<li className="nhsuk-tabs__list-item">
<a className="nhsuk-tabs__tab" href={`#${id}`}>
{children}
</a>
</li>
);

const TabContents: React.FC<TabContentsProps> = ({ id, children }) => (
<div className="nhsuk-tabs__panel" id={id}>
{children}
</div>
);

interface Tabs extends React.FC<TabsProps> {
Title: React.FC<TabTitleProps>;
List: React.FC<TabListProps>;
ListItem: React.FC<TabListItemProps>;
Contents: React.FC<TabContentsProps>;
}

const Tabs: Tabs = ({ className, children, ...rest }) => {
useEffect(() => {
TabsJs();
}, []);

return (
<div className={classNames('nhsuk-tabs', className)} data-module="nhsuk-tabs" {...rest}>
{children}
</div>
);
};

Tabs.Title = TabTitle;
Tabs.List = TabList;
Tabs.ListItem = TabListItem;
Tabs.Contents = TabContents;

export default Tabs;
Loading

0 comments on commit d2b7f4b

Please sign in to comment.