Skip to content

Commit 980fe8a

Browse files
ThomasThomas
authored andcommitted
Add Card Element
1 parent 3345d95 commit 980fe8a

File tree

17 files changed

+684
-23
lines changed

17 files changed

+684
-23
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"eslint-plugin-react-hooks": "^1.7.0",
4646
"jest": "^24.9.0",
4747
"jest-axe": "^3.4.0",
48-
"nhsuk-frontend": "^3.1.0",
48+
"nhsuk-frontend": "^4.1.0",
4949
"node-sass": "^4.14.1",
5050
"prettier": "^1.18.2",
5151
"react": "^16.9.3",
@@ -62,7 +62,7 @@
6262
"typescript": "^3.9.5"
6363
},
6464
"peerDependencies": {
65-
"nhsuk-frontend": "^3.0.0",
65+
"nhsuk-frontend": "^4.1.0",
6666
"react": "^16.9.0",
6767
"react-dom": "^16.9.0"
6868
},

src/components/action-link/ActionLink.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React from 'react';
22
import classNames from 'classnames';
33
import { ArrowRightCircle } from '../icons';
4-
import type { AsElementLink } from 'util/types/LinkTypes';
4+
import type { AsElementLink } from '../../util/types/LinkTypes';
55

6-
const ActionLink: React.FC<AsElementLink<HTMLAnchorElement>> = ({
7-
children,
8-
asElement: Component = 'a',
9-
className,
10-
...rest
6+
const ActionLink: React.FC<AsElementLink<HTMLAnchorElement>> = ({
7+
children,
8+
asElement: Component = 'a',
9+
className,
10+
...rest
1111
}) => (
1212
<div className="nhsuk-action-link">
1313
<Component className={classNames('nhsuk-action-link__link', className)} {...rest}>

src/components/card/Card.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { HTMLProps } from 'react';
2+
import classNames from 'classnames';
3+
import CardContext from './CardContext';
4+
import CardContent from './components/CardContent';
5+
import CardDescription from './components/CardDescription';
6+
import CardImage from './components/CardImage';
7+
import CardLink from './components/CardLink';
8+
import CardHeading from './components/CardHeading';
9+
import CardGroup from './components/CardGroup';
10+
import CardGroupItem from './components/CardGroupItem';
11+
12+
interface CardProps extends HTMLProps<HTMLDivElement> {
13+
clickable?: boolean;
14+
feature?: boolean;
15+
}
16+
17+
interface ICard extends React.FC<CardProps> {
18+
Content: typeof CardContent;
19+
Description: typeof CardDescription;
20+
Image: typeof CardImage;
21+
Link: typeof CardLink;
22+
Heading: typeof CardHeading;
23+
Group: typeof CardGroup;
24+
GroupItem: typeof CardGroupItem;
25+
}
26+
27+
const Card: ICard = ({ className, clickable, children, feature, ...rest }) => (
28+
<div
29+
className={classNames(
30+
'nhsuk-card',
31+
{ 'nhsuk-card--clickable': clickable },
32+
{ 'nhsuk-card--feature': feature },
33+
className,
34+
)}
35+
{...rest}
36+
>
37+
<CardContext.Provider value={{ feature: Boolean(feature) }}>{children}</CardContext.Provider>
38+
</div>
39+
);
40+
41+
Card.defaultProps = {
42+
feature: false,
43+
};
44+
45+
Card.Heading = CardHeading;
46+
Card.Description = CardDescription;
47+
Card.Image = CardImage;
48+
Card.Link = CardLink;
49+
Card.Content = CardContent;
50+
Card.Group = CardGroup;
51+
Card.GroupItem = CardGroupItem;
52+
53+
export default Card;

src/components/card/CardContext.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
export interface ICardContext {
4+
feature: boolean;
5+
}
6+
7+
const CardContext = React.createContext<ICardContext>({
8+
feature: false,
9+
});
10+
11+
export default CardContext;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { mount } from 'enzyme';
2+
import React from 'react';
3+
import Card from '../Card';
4+
5+
describe('Card', () => {
6+
it('matches snapshot', () => {
7+
const wrapper = mount(
8+
<Card>
9+
<Card.Image src="imageSrc" alt="imageAlt" />
10+
<Card.Content>
11+
<Card.Heading>If you need help now but it&apos;s not an emergency</Card.Heading>
12+
<Card.Description>
13+
Go to <a href="#">111.nhs.uk</a> or <a href="#">call 111</a>
14+
</Card.Description>
15+
</Card.Content>
16+
</Card>,
17+
);
18+
19+
expect(wrapper).toMatchSnapshot();
20+
21+
wrapper.unmount();
22+
});
23+
24+
it('can render Card.Link as different elements', () => {
25+
const wrapper = mount(
26+
<Card clickable>
27+
<Card.Content>
28+
<Card.Heading>
29+
<Card.Link asElement="button" type="button">
30+
Click me!
31+
</Card.Link>
32+
</Card.Heading>
33+
</Card.Content>
34+
</Card>,
35+
);
36+
expect(
37+
wrapper.find(Card.Link).containsMatchingElement(
38+
<button type="button" className="nhsuk-card__link">
39+
Click me!
40+
</button>,
41+
),
42+
).toBeTruthy();
43+
});
44+
45+
it('adds clickable classes', () => {
46+
const wrapper = mount(
47+
<Card clickable>
48+
<Card.Content>
49+
<Card.Heading className="nhsuk-heading-m">
50+
<Card.Link href="#">Introduction to care and support</Card.Link>
51+
</Card.Heading>
52+
<Card.Description>
53+
A quick guide for people who have care and support needs and their carers
54+
</Card.Description>
55+
</Card.Content>
56+
</Card>,
57+
);
58+
59+
expect(wrapper.find('div.nhsuk-card').props().className).toBe(
60+
'nhsuk-card nhsuk-card--clickable',
61+
);
62+
63+
wrapper.unmount();
64+
});
65+
66+
it('adds feature classes to all elements', () => {
67+
const wrapper = mount(
68+
<Card feature>
69+
<Card.Content>
70+
<Card.Heading>Feature card heading</Card.Heading>
71+
<Card.Description>Feature card description</Card.Description>
72+
</Card.Content>
73+
</Card>,
74+
);
75+
76+
expect(wrapper.find('div.nhsuk-card').props().className).toBe('nhsuk-card nhsuk-card--feature');
77+
expect(wrapper.find('div.nhsuk-card__content').props().className).toBe(
78+
'nhsuk-card__content nhsuk-card__content--feature',
79+
);
80+
expect(wrapper.find('h2.nhsuk-card__heading').props().className).toBe(
81+
'nhsuk-card__heading nhsuk-card__heading--feature',
82+
);
83+
84+
wrapper.unmount();
85+
});
86+
87+
describe('Card.Group', () => {
88+
it('matches snapshot', () => {
89+
const wrapper = mount(
90+
<Card.Group>
91+
<Card.GroupItem width="one-half">
92+
<Card>
93+
<Card.Content>
94+
<Card.Heading>Test Card 1</Card.Heading>
95+
<Card.Description>Test Card 1 Description</Card.Description>
96+
</Card.Content>
97+
</Card>
98+
</Card.GroupItem>
99+
<Card.GroupItem width="one-half">
100+
<Card>
101+
<Card.Content>
102+
<Card.Heading>Test Card 2</Card.Heading>
103+
<Card.Description>Test Card 2 Description</Card.Description>
104+
</Card.Content>
105+
</Card>
106+
</Card.GroupItem>
107+
</Card.Group>,
108+
);
109+
110+
expect(wrapper).toMatchSnapshot();
111+
112+
wrapper.unmount();
113+
});
114+
});
115+
});
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Card Card.Group matches snapshot 1`] = `
4+
<CardGroup>
5+
<Row
6+
className="nhsuk-card-group"
7+
>
8+
<div
9+
className="nhsuk-grid-row nhsuk-card-group"
10+
>
11+
<CardGroupItem
12+
width="one-half"
13+
>
14+
<Col
15+
className="nhsuk-card-group__item"
16+
width="one-half"
17+
>
18+
<div
19+
className="nhsuk-grid-column-one-half nhsuk-card-group__item"
20+
>
21+
<Card
22+
feature={false}
23+
>
24+
<div
25+
className="nhsuk-card"
26+
>
27+
<CardContent>
28+
<div
29+
className="nhsuk-card__content"
30+
>
31+
<CardHeading
32+
headingLevel="h2"
33+
>
34+
<HeadingLevel
35+
className="nhsuk-card__heading"
36+
headingLevel="h2"
37+
>
38+
<h2
39+
className="nhsuk-card__heading"
40+
>
41+
Test Card 1
42+
</h2>
43+
</HeadingLevel>
44+
</CardHeading>
45+
<CardDescription>
46+
<p
47+
className="nhsuk-card__description"
48+
>
49+
Test Card 1 Description
50+
</p>
51+
</CardDescription>
52+
</div>
53+
</CardContent>
54+
</div>
55+
</Card>
56+
</div>
57+
</Col>
58+
</CardGroupItem>
59+
<CardGroupItem
60+
width="one-half"
61+
>
62+
<Col
63+
className="nhsuk-card-group__item"
64+
width="one-half"
65+
>
66+
<div
67+
className="nhsuk-grid-column-one-half nhsuk-card-group__item"
68+
>
69+
<Card
70+
feature={false}
71+
>
72+
<div
73+
className="nhsuk-card"
74+
>
75+
<CardContent>
76+
<div
77+
className="nhsuk-card__content"
78+
>
79+
<CardHeading
80+
headingLevel="h2"
81+
>
82+
<HeadingLevel
83+
className="nhsuk-card__heading"
84+
headingLevel="h2"
85+
>
86+
<h2
87+
className="nhsuk-card__heading"
88+
>
89+
Test Card 2
90+
</h2>
91+
</HeadingLevel>
92+
</CardHeading>
93+
<CardDescription>
94+
<p
95+
className="nhsuk-card__description"
96+
>
97+
Test Card 2 Description
98+
</p>
99+
</CardDescription>
100+
</div>
101+
</CardContent>
102+
</div>
103+
</Card>
104+
</div>
105+
</Col>
106+
</CardGroupItem>
107+
</div>
108+
</Row>
109+
</CardGroup>
110+
`;
111+
112+
exports[`Card matches snapshot 1`] = `
113+
<Card
114+
feature={false}
115+
>
116+
<div
117+
className="nhsuk-card"
118+
>
119+
<CardImage
120+
alt="imageAlt"
121+
src="imageSrc"
122+
>
123+
<img
124+
alt="imageAlt"
125+
className="nhsuk-card__img"
126+
src="imageSrc"
127+
/>
128+
</CardImage>
129+
<CardContent>
130+
<div
131+
className="nhsuk-card__content"
132+
>
133+
<CardHeading
134+
headingLevel="h2"
135+
>
136+
<HeadingLevel
137+
className="nhsuk-card__heading"
138+
headingLevel="h2"
139+
>
140+
<h2
141+
className="nhsuk-card__heading"
142+
>
143+
If you need help now but it's not an emergency
144+
</h2>
145+
</HeadingLevel>
146+
</CardHeading>
147+
<CardDescription>
148+
<p
149+
className="nhsuk-card__description"
150+
>
151+
Go to
152+
<a
153+
href="#"
154+
>
155+
111.nhs.uk
156+
</a>
157+
or
158+
<a
159+
href="#"
160+
>
161+
call 111
162+
</a>
163+
</p>
164+
</CardDescription>
165+
</div>
166+
</CardContent>
167+
</div>
168+
</Card>
169+
`;

0 commit comments

Comments
 (0)