diff --git a/src/components/content-presentation/tabs/Tabs.tsx b/src/components/content-presentation/tabs/Tabs.tsx new file mode 100644 index 00000000..5c0398fa --- /dev/null +++ b/src/components/content-presentation/tabs/Tabs.tsx @@ -0,0 +1,72 @@ +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; + +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 = ({ children, headingLevel = 'h2' }) => ( + + {children} + +); + +const TabList: React.FC = ({ children }) => ( +
    {children}
+); + +const TabListItem: React.FC = ({ id, children }) => ( +
  • + + {children} + +
  • +); + +const TabContents: React.FC = ({ id, children }) => ( +
    + {children} +
    +); + +interface Tabs extends React.FC { + Title: React.FC; + List: React.FC; + ListItem: React.FC; + Contents: React.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..4480e938 --- /dev/null +++ b/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import Tabs from '../Tabs'; + +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(); + }); +}); 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/form-elements/character-count/CharacterCount.tsx b/src/components/form-elements/character-count/CharacterCount.tsx index 156acedc..affc11e8 100644 --- a/src/components/form-elements/character-count/CharacterCount.tsx +++ b/src/components/form-elements/character-count/CharacterCount.tsx @@ -7,7 +7,7 @@ export enum CharacterCountType { Words, } -type CharacterCountProps = { +type CharacterCountProps = React.HTMLAttributes & { children: React.ReactNode; maxLength: number; countType: CharacterCountType; @@ -21,6 +21,7 @@ const CharacterCount: React.FC = ({ countType, textAreaId, thresholdPercent, + ...rest }) => { useEffect(() => { CharacterCountJs(); @@ -28,8 +29,8 @@ const CharacterCount: React.FC = ({ const characterCountProps: HTMLAttributesWithData = countType === CharacterCountType.Characters - ? { ['data-maxlength']: maxLength } - : { ['data-maxwords']: maxLength }; + ? { ...rest, ['data-maxlength']: maxLength } + : { ...rest, ['data-maxwords']: maxLength }; if (thresholdPercent) { characterCountProps['data-threshold'] = thresholdPercent; diff --git a/src/global.d.ts b/src/global.d.ts index 118a29d0..c844d6e4 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,3 +1,4 @@ declare module 'nhsuk-frontend/packages/components/header/header.js'; declare module 'nhsuk-frontend/packages/components/checkboxes/checkboxes.js'; declare module 'nhsuk-frontend/packages/components/character-count/character-count.js'; +declare module 'nhsuk-frontend/packages/components/tabs/tabs.js'; diff --git a/src/index.ts b/src/index.ts index ec7d8cc1..4615494b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,6 +52,7 @@ export { default as Select } from './components/form-elements/select'; export { default as SkipLink } from './components/navigation/skip-link'; export { default as SummaryList } from './components/content-presentation/summary-list'; export { default as Table } from './components/content-presentation/table'; +export { default as Tabs } from './components/content-presentation/tabs'; export { default as Tag } from './components/content-presentation/tag'; export { default as Textarea } from './components/form-elements/textarea'; export { LedeText, BodyText } from './components/typography'; 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 index 4f76f9aa..0ee774ae 100644 --- a/stories/Form Elements/CharacterCount.stories.tsx +++ b/stories/Form Elements/CharacterCount.stories.tsx @@ -2,6 +2,14 @@ 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, @@ -32,6 +40,11 @@ export const Standard: Story = { ), }; +/** + * Sometimes, rather than counting the number of characters, it is useful to count the number of words instead. + * + * Use the `countType` prop to vary this behaviour. + */ export const WordCountLimit: Story = { render: () => ( (