Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add new components since nhsuk-frontend v5 #202

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
JoshuaBates-NHS marked this conversation as resolved.
Show resolved Hide resolved

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> {

Check warning on line 15 in src/components/content-presentation/do-and-dont-list/DoAndDontList.tsx

View workflow job for this annotation

GitHub Actions / build

Caution: `React` also has a named export `FC`. Check if you meant to write `import {FC} from 'react'` instead
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 @@
{ '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 @@
);
};

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> {

Check warning on line 49 in src/components/content-presentation/tabs/Tabs.tsx

View workflow job for this annotation

GitHub Actions / build

Caution: `React` also has a named export `FC`. Check if you meant to write `import {FC} from 'react'` instead
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
Loading