From 7007b2c984199af9caf23fe1250abdadcb7c475e Mon Sep 17 00:00:00 2001 From: Jake Barron Date: Mon, 18 Mar 2024 16:16:51 +0000 Subject: [PATCH] New components WIP --- docs/upgrade-to-4.0.md | 114 ++++++++++++ .../content-presentation/tabs/Tabs.tsx | 72 ++++++++ .../tabs/__tests__/Tabs.test.tsx | 32 ++++ .../__snapshots__/Tabs.test.tsx.snap | 99 +++++++++++ .../content-presentation/tabs/index.ts | 3 + .../character-count/CharacterCount.tsx | 54 ++++++ .../__tests__/CharacterCount.test.tsx | 87 ++++++++++ .../CharacterCount.test.tsx.snap | 59 +++++++ .../form-elements/character-count/index.ts | 4 + .../form-elements/checkboxes/Checkboxes.tsx | 7 + .../checkboxes/components/Box.tsx | 17 +- .../checkboxes/components/Divider.tsx | 11 ++ .../form-elements/textarea/Textarea.tsx | 6 +- .../__snapshots__/Icons.test.tsx.snap | 36 ++++ src/components/icons/index.ts | 1 + .../icons/individual/ChevronRightCircle.tsx | 25 +++ src/components/navigation/card/Card.tsx | 21 ++- src/components/navigation/card/CardContext.ts | 4 + .../navigation/card/__tests__/Card.test.tsx | 35 ++++ .../card/components/CardContent.tsx | 4 +- src/global.d.ts | 3 + src/index.ts | 5 + src/util/types/NHSUKTypes.ts | 12 +- stories/Components/Checkboxes.stories.tsx | 162 +++++++++++------- stories/Content Presentation/Tabs.stories.tsx | 74 ++++++++ .../Form Elements/CharacterCount.stories.tsx | 87 ++++++++++ stories/Form Elements/Checkboxes.stories.tsx | 20 +-- stories/Navigation/Card.stories.tsx | 28 +++ 28 files changed, 998 insertions(+), 84 deletions(-) create mode 100644 src/components/content-presentation/tabs/Tabs.tsx create mode 100644 src/components/content-presentation/tabs/__tests__/Tabs.test.tsx create mode 100644 src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap create mode 100644 src/components/content-presentation/tabs/index.ts create mode 100644 src/components/form-elements/character-count/CharacterCount.tsx create mode 100644 src/components/form-elements/character-count/__tests__/CharacterCount.test.tsx create mode 100644 src/components/form-elements/character-count/__tests__/__snapshots__/CharacterCount.test.tsx.snap create mode 100644 src/components/form-elements/character-count/index.ts create mode 100644 src/components/form-elements/checkboxes/components/Divider.tsx create mode 100644 src/components/icons/individual/ChevronRightCircle.tsx create mode 100644 stories/Content Presentation/Tabs.stories.tsx create mode 100644 stories/Form Elements/CharacterCount.stories.tsx diff --git a/docs/upgrade-to-4.0.md b/docs/upgrade-to-4.0.md index 645a04e6..d7605365 100644 --- a/docs/upgrade-to-4.0.md +++ b/docs/upgrade-to-4.0.md @@ -90,3 +90,117 @@ You can now specify prefixes and/or suffixes for your text inputs. These are exp ``` ``` + +### 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. + +``` + + Sore throat + Runny nose + Muscle or joint pain + + + None + + +``` + +### New component - Character Count + +See [the Digital Service Manual](https://service-manual.nhs.uk/design-system/components/character-count) for information. +Usage: + +``` + + + )} ); diff --git a/src/components/icons/__tests__/__snapshots__/Icons.test.tsx.snap b/src/components/icons/__tests__/__snapshots__/Icons.test.tsx.snap index 3038a894..f5c12390 100644 --- a/src/components/icons/__tests__/__snapshots__/Icons.test.tsx.snap +++ b/src/components/icons/__tests__/__snapshots__/Icons.test.tsx.snap @@ -106,6 +106,42 @@ exports[`Icons all icons match snapshots: ChevronRight 1`] = ` `; +exports[`Icons all icons match snapshots: ChevronRightCircle 1`] = ` +
+ +
+`; + exports[`Icons all icons match snapshots: Close 1`] = `
( + +); diff --git a/src/components/navigation/card/Card.tsx b/src/components/navigation/card/Card.tsx index f5305f31..f456e037 100644 --- a/src/components/navigation/card/Card.tsx +++ b/src/components/navigation/card/Card.tsx @@ -12,6 +12,8 @@ import CardGroupItem from './components/CardGroupItem'; interface CardProps extends HTMLProps { clickable?: boolean; feature?: boolean; + primary?: boolean; + secondary?: boolean; } interface ICard extends React.FC { @@ -25,18 +27,33 @@ interface ICard extends React.FC { } const Card: ICard = ({ - className, clickable, children, feature = false, ...rest + className, + clickable, + children, + feature = false, + primary = false, + secondary = false, + ...rest }) => (
- {children} + + {children} +
); diff --git a/src/components/navigation/card/CardContext.ts b/src/components/navigation/card/CardContext.ts index a07f9250..d72a13e9 100644 --- a/src/components/navigation/card/CardContext.ts +++ b/src/components/navigation/card/CardContext.ts @@ -2,10 +2,14 @@ import React from 'react'; export interface ICardContext { feature: boolean; + primary: boolean; + secondary: boolean; } const CardContext = React.createContext({ feature: false, + primary: false, + secondary: false, }); export default CardContext; diff --git a/src/components/navigation/card/__tests__/Card.test.tsx b/src/components/navigation/card/__tests__/Card.test.tsx index 890dcb5f..560a0aaf 100644 --- a/src/components/navigation/card/__tests__/Card.test.tsx +++ b/src/components/navigation/card/__tests__/Card.test.tsx @@ -76,6 +76,41 @@ describe('Card', () => { ).toBeTruthy(); }); + it('adds primary class to card contents', () => { + const { container } = render( + + + Feature card heading + Feature card description + + , + ); + + expect( + container.querySelector( + 'div.nhsuk-card__content.nhsuk-card__content.nhsuk-card__content--primary', + ), + ).toBeTruthy(); + }); + + it('adds secondary classes to card and contents', () => { + const { container } = render( + + + Feature card heading + Feature card description + + , + ); + + expect(container.querySelector('div.nhsuk-card.nhsuk-card.nhsuk-card--secondary')).toBeTruthy(); + expect( + container.querySelector( + 'div.nhsuk-card__content.nhsuk-card__content.nhsuk-card__content--secondary', + ), + ).toBeTruthy(); + }); + describe('Card.Group', () => { it('matches snapshot', () => { const { container } = render( diff --git a/src/components/navigation/card/components/CardContent.tsx b/src/components/navigation/card/components/CardContent.tsx index 04de1460..1e9030e0 100644 --- a/src/components/navigation/card/components/CardContent.tsx +++ b/src/components/navigation/card/components/CardContent.tsx @@ -3,12 +3,14 @@ import classNames from 'classnames'; import CardContext from '../CardContext'; const CardContent: React.FC> = ({ className, ...rest }) => { - const { feature } = useContext(CardContext); + const { feature, primary, secondary } = useContext(CardContext); return (
= React.HTMLAttributes & { + [key: `data-${string}`]: unknown; +}; diff --git a/stories/Components/Checkboxes.stories.tsx b/stories/Components/Checkboxes.stories.tsx index 15ae1752..dd7be8f6 100644 --- a/stories/Components/Checkboxes.stories.tsx +++ b/stories/Components/Checkboxes.stories.tsx @@ -38,91 +38,121 @@ export default meta; type Story = StoryObj; Checkboxes.Box.displayName = 'Checkboxes.Box'; +Checkboxes.Divider.displayName = 'Checkboxes.Divider'; export const Standard: Story = { render: (args) => ( -
- What is your nationality? - - British - Irish - Citizen of another country - -
+
+
+ What is your nationality? + + British + Irish + Citizen of another country + +
+
), }; export const WithHintText: Story = { render: (args) => ( -
- How do you want to sign in? - - - Sign in with Government Gateway - - - Sign in with NHS.UK login - - -
+
+
+ How do you want to sign in? + + + Sign in with Government Gateway + + + Sign in with NHS.UK login + + +
+
), }; export const WithDisabledItem: Story = { render: (args) => ( - - Red - Green - - Blue - - +
+ + Red + Green + + Blue + + +
), }; export const WithConditionalContent: Story = { render: (args) => ( -
- - Which types of waste do you transport regularly? - - - This includes rocks and earth.

} value="mines"> - Waste from mines or quarries -
-
-
+
+
+ + Which types of waste do you transport regularly? + + + This includes rocks and earth.

} value="mines"> + Waste from mines or quarries +
+
+
+
), }; export const WithLegendAsPageHeading: Story = { render: (args) => ( -
- - Which types of waste do you transport regularly? - - - Waste from animal carcasses - Waste from mines or quarries - Farm or agricultural waste - -
+
+
+ + Which types of waste do you transport regularly? + + + Waste from animal carcasses + Waste from mines or quarries + Farm or agricultural waste + +
+
+ ), +}; + +export const WithExclusiveNoneOption: Story = { + render: (args) => ( +
+
+ Do you have any of these symptoms? + + Sore throat + Runny nose + Muscle or joint pain + + + None + + +
+
), }; @@ -131,7 +161,7 @@ export const WithErrorBoolean: Story = { // eslint-disable-next-line react-hooks/rules-of-hooks const [errorToggle, setErrorToggle] = React.useState(true); return ( - <> +
Which types of waste do you transport regularly? @@ -150,7 +180,7 @@ export const WithErrorBoolean: Story = { > Toggle Error - + ); }, @@ -162,7 +192,7 @@ export const WithErrorString: Story = { // eslint-disable-next-line react-hooks/rules-of-hooks const [error, setError] = React.useState('Please select an option'); return ( - <> +
Which types of waste do you transport regularly? @@ -178,7 +208,7 @@ export const WithErrorString: Story = { value={error} onChange={(e) => setError(e.currentTarget.value)} /> - + ); }, name: 'With Error (String)', diff --git a/stories/Content Presentation/Tabs.stories.tsx b/stories/Content Presentation/Tabs.stories.tsx new file mode 100644 index 00000000..7cb0201e --- /dev/null +++ b/stories/Content Presentation/Tabs.stories.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Tabs } from '../../src'; +import { Meta, StoryObj } from '@storybook/react'; + +/** + * The tabs component lets users navigate between related sections of content, displaying 1 section at a time. + * + * This component can be found in the `nhsuk-frontend` repository here. + * + * Further information about this component can be found in the NHS digital service manual. + */ + +const meta: Meta = { + title: 'Form Elements/Tabs', + component: Tabs, +}; + +export default meta; +type Story = StoryObj; + +export const Standard: Story = { + render: () => ( + + Contents + + Past day + Past week + Past month + + + +
Past day contents go here
+
+ + +
Past week contents go here
+
+ + +
Past month contents go here
+
+
+ ), +}; + +/** + * There is a hidden heading which is useful for accessibility concerns and screen readers. + * + * This heading is also visible on small screens and if the user has JavaScript disabled. + */ +export const DifferentAccessibleHeading: Story = { + render: () => ( + + Tabs title + + Past day + Past week + Past month + + + +
Past day contents go here
+
+ + +
Past week contents go here
+
+ + +
Past month contents go here
+
+
+ ), +}; diff --git a/stories/Form Elements/CharacterCount.stories.tsx b/stories/Form Elements/CharacterCount.stories.tsx new file mode 100644 index 00000000..0ee774ae --- /dev/null +++ b/stories/Form Elements/CharacterCount.stories.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { CharacterCount, CharacterCountType, HintText, Label, Textarea } from '../../src'; +import { Meta, StoryObj } from '@storybook/react'; + +/** + * Help users know how much text they can enter when there is a limit on the number of characters. + * + * This component can be found in the `nhsuk-frontend` repository here. + * + * Further information about this component can be found in the NHS digital service manual. + */ + +const meta: Meta = { + title: 'Form Elements/CharacterCount', + component: CharacterCount, +}; + +export default meta; +type Story = StoryObj; + +export const Standard: Story = { + render: () => ( + + + + Do not include personal information like your name, date of birth or NHS number. + + + + ), +}; diff --git a/stories/Form Elements/Checkboxes.stories.tsx b/stories/Form Elements/Checkboxes.stories.tsx index e006a82c..fb0dad7c 100644 --- a/stories/Form Elements/Checkboxes.stories.tsx +++ b/stories/Form Elements/Checkboxes.stories.tsx @@ -54,7 +54,7 @@ export const NoIDSupplied: Story = { }, [checkbox1Ref.current, checkbox2Ref.current, checkbox3Ref.current]); return ( -
+

Scenario: No ID Supplied

Expected Behaviour
    @@ -94,7 +94,7 @@ export const NoIDSupplied: Story = { Box 2 Box 3 -
+ ); }, }; @@ -138,7 +138,7 @@ export const NameSupplied: Story = { }, [checkbox1Ref.current, checkbox2Ref.current, checkbox3Ref.current]); return ( -
+

Scenario: Name Supplied

Expected Behaviour
    @@ -178,7 +178,7 @@ export const NameSupplied: Story = { Box 2 Box 3 -
+ ); }, }; @@ -222,7 +222,7 @@ export const IDPrefixSupplied: Story = { }, [checkbox1Ref.current, checkbox2Ref.current, checkbox3Ref.current]); return ( -
+

Scenario: ID Prefix Supplied

Expected Behaviour
    @@ -263,7 +263,7 @@ export const IDPrefixSupplied: Story = { Box 2 Box 3 -
+ ); }, }; @@ -307,7 +307,7 @@ export const IDPrefixAndNameSupplied: Story = { }, [checkbox1Ref.current, checkbox2Ref.current, checkbox3Ref.current]); return ( -
+

Scenario: ID Prefix and Name Supplied

Expected Behaviour
    @@ -348,7 +348,7 @@ export const IDPrefixAndNameSupplied: Story = { Box 2 Box 3 -
+ ); }, }; @@ -381,7 +381,7 @@ export const OnChangeAndOnInputHandlers: Story = { }; return ( -
+

Scenario: onChange and onInput handlers are bound without any other props

Expected Behaviour
    @@ -409,7 +409,7 @@ export const OnChangeAndOnInputHandlers: Story = {
  • {event}
  • ))}
-
+ ); }, }; diff --git a/stories/Navigation/Card.stories.tsx b/stories/Navigation/Card.stories.tsx index 96d2b390..d50acfd6 100644 --- a/stories/Navigation/Card.stories.tsx +++ b/stories/Navigation/Card.stories.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Card } from '../../src'; import { Meta, StoryObj } from '@storybook/react'; import { ColWidth } from '../../src/util/types/NHSUKTypes'; +import { ChevronRightCircle } from '../../src/components/icons'; const meta: Meta = { title: 'Navigation/Card', @@ -80,6 +81,33 @@ export const FeatureCard: Story = { ), }; +export const PrimaryCardWithChevron: Story = { + render: () => ( + + + + Primary card heading + + Primary card description + + + + ), +}; + +export const SecondaryCard: Story = { + render: () => ( + + + + Secondary card heading + + Secondary card description + + + ), +}; + export const CardGroup: Story = { args: { width: 'one-half' }, argTypes: {