Skip to content
This repository was archived by the owner on Feb 27, 2024. It is now read-only.

Commit 4887d9c

Browse files
author
Greg Rickaby
authored
Merge pull request #177 from WebDevStudios/feature/149-card
Feature/149 card
2 parents 376f610 + 470c5c0 commit 4887d9c

File tree

9 files changed

+309
-37
lines changed

9 files changed

+309
-37
lines changed

components/atoms/Heading/Heading.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import createMarkup from '@/functions/createMarkup'
2+
import PropTypes from 'prop-types'
3+
import React from 'react'
4+
5+
/**
6+
* Render the Heading component.
7+
*
8+
* @param {object} props The props object.
9+
* @param {string} props.children The elements or text you'd like to render inside the heading.
10+
* @param {string} props.className The optional classname.
11+
* @param {string} props.id The optional ID.
12+
* @param {string} props.tag The tag name you'd like the heading to render as.
13+
* @return {Element} The Heading element.
14+
*/
15+
export default function Heading({children, className, id, tag}) {
16+
if (typeof children === 'string') {
17+
return React.createElement(tag, {
18+
className,
19+
id,
20+
dangerouslySetInnerHTML: createMarkup(children)
21+
})
22+
} else {
23+
return React.createElement(
24+
tag,
25+
{
26+
className,
27+
id
28+
},
29+
children
30+
)
31+
}
32+
}
33+
34+
Heading.propTypes = {
35+
children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
36+
className: PropTypes.string,
37+
id: PropTypes.string,
38+
tag: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
39+
}
40+
41+
Heading.defaultProps = {
42+
tag: 'h1'
43+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {Meta, Story, Canvas} from '@storybook/addon-docs/blocks'
2+
3+
import Heading from '.'
4+
5+
<Meta title="Components/Atoms/Heading" component={Heading} />
6+
7+
# Heading
8+
9+
Use this component inside other components when you need to dynamically set the heading tag that's used.
10+
For example, a card's heading might need to be `<h2>` in one place but an `<h3>` when used somewhere else.
11+
12+
This component is deliberatley unstyled. Pass down class names with the `className` prop.
13+
14+
<Canvas>
15+
<Story name="Component">
16+
<Heading>This is a heading</Heading>
17+
</Story>
18+
</Canvas>
19+
20+
# Tag
21+
22+
`<h1>` through `<h6>` tags are supported.
23+
24+
<Canvas>
25+
<Story name="Tag">
26+
<Heading tag="h1">This is an h1</Heading>
27+
<Heading tag="h2">This is an h2</Heading>
28+
<Heading tag="h3">This is an h3</Heading>
29+
<Heading tag="h4">This is an h4</Heading>
30+
<Heading tag="h5">This is an h5</Heading>
31+
<Heading tag="h6">This is an h6</Heading>
32+
</Story>
33+
</Canvas>
34+
35+
# Markup Support
36+
37+
This component supports various types of children content including strings of HTML and standard JSX. The following shows both content types being passed in as children.
38+
39+
<Canvas>
40+
<Story name="Markup Support">
41+
<Heading tag="h1">{'<strong>String</strong> of HTML Example'}</Heading>
42+
<Heading tag="h1">
43+
<strong>Object</strong> of JSX Example
44+
</Heading>
45+
</Story>
46+
</Canvas>
47+
48+
## Controls
49+
50+
export const Template = (args) => <Heading {...args}>{args.children}</Heading>
51+
52+
<Canvas>
53+
<Story
54+
name="Controls"
55+
args={{
56+
tag: 'h1',
57+
children: 'This is a heading'
58+
}}
59+
>
60+
{Template.bind({})}
61+
</Story>
62+
</Canvas>

components/atoms/Heading/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {default} from './Heading'

components/molecules/Card/Card.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import Button from '@/components/atoms/Button'
2+
import Heading from '@/components/atoms/Heading'
3+
import RichText from '@/components/atoms/RichText'
4+
import cn from 'classnames'
5+
import Link from 'next/link'
6+
import PropTypes from 'prop-types'
7+
import React from 'react'
8+
import styles from './Card.module.css'
9+
10+
/**
11+
* Render the Card component.
12+
*
13+
* @param {object} props Card component props.
14+
* @param {string} props.body Card body text.
15+
* @param {string} props.buttonText The text for the cta button.
16+
* @param {string} props.className Optional classNames.
17+
* @param {object} props.image The image object.
18+
* @param {string} props.meta The card metadata string.
19+
* @param {string} props.timestamp The card timestamp.
20+
* @param {string} props.title The card title.
21+
* @param {string} props.url The url.
22+
* @return {Element} The Card component.
23+
*/
24+
export default function Card({
25+
body,
26+
buttonText,
27+
className,
28+
image,
29+
meta,
30+
timestamp,
31+
title,
32+
url
33+
}) {
34+
return (
35+
<div className={cn(styles.card, className)}>
36+
{image && image.sourceUrl && (
37+
<div className={styles.image}>
38+
<img
39+
draggable="false"
40+
src={image.sourceUrl}
41+
alt={`${image.altText ?? title}`}
42+
loading="lazy"
43+
height={image.height}
44+
width={image.width}
45+
/>
46+
</div>
47+
)}
48+
<div className={styles.content}>
49+
{meta && <p className={styles.meta}>{meta}</p>}
50+
{title &&
51+
(url ? (
52+
<Link href={url}>
53+
<a>
54+
<Heading className={styles.title}>{title}</Heading>
55+
</a>
56+
</Link>
57+
) : (
58+
<Heading className={styles.title}>{title}</Heading>
59+
))}
60+
{body && <RichText className={styles.body}>{body}</RichText>}
61+
</div>
62+
<div className={styles.footer}>
63+
{timestamp && (
64+
<p className={styles.timestamp}>
65+
<time>{timestamp}</time>
66+
</p>
67+
)}
68+
{buttonText && url && (
69+
<Button
70+
className={styles.button}
71+
url={url}
72+
text={buttonText}
73+
type="secondary"
74+
size="md"
75+
/>
76+
)}
77+
</div>
78+
</div>
79+
)
80+
}
81+
82+
Card.propTypes = {
83+
body: PropTypes.string,
84+
buttonText: PropTypes.string,
85+
className: PropTypes.string,
86+
headingLevel: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
87+
image: PropTypes.shape({
88+
sourceUrl: PropTypes.string,
89+
altText: PropTypes.string,
90+
height: PropTypes.string,
91+
width: PropTypes.string
92+
}),
93+
meta: PropTypes.string,
94+
title: PropTypes.string,
95+
timestamp: PropTypes.string,
96+
url: PropTypes.string
97+
}
98+
99+
Card.defaultProps = {
100+
headingLevel: 'h2'
101+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.card {
2+
@apply flex flex-col;
3+
4+
& .image {
5+
@apply mb-2 relative h-0 w-full rounded;
6+
7+
padding-top: 64%; /* Aspect ratio box - https://css-tricks.com/aspect-ratio-boxes */
8+
9+
& img {
10+
@apply absolute top-0 left-0 w-full h-full object-cover rounded;
11+
}
12+
}
13+
14+
& .content {
15+
& .meta {
16+
@apply mb-2 text-sm;
17+
}
18+
19+
& .title {
20+
@apply mb-2 font-bold;
21+
}
22+
23+
& .body {
24+
@apply mb-2;
25+
}
26+
}
27+
28+
& .footer {
29+
@apply mt-auto flex justify-between items-center;
30+
31+
& .timestamp {
32+
@apply text-sm;
33+
}
34+
}
35+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {Canvas, Meta, Story} from '@storybook/addon-docs/blocks'
2+
import Card from './'
3+
4+
<Meta title="Components/Molecules/Card" component={Card} />
5+
6+
# Card
7+
8+
Use this component to display a card.
9+
10+
<Canvas>
11+
<Story name="Card">
12+
<Card
13+
image={{
14+
sourceUrl:
15+
'https://images.unsplash.com/photo-1610991149688-c1321006bcc1?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1110&q=60'
16+
}}
17+
meta="This is the meta"
18+
title="This is the card title"
19+
body="This is the card body"
20+
timestamp="May 29, 2021"
21+
ctaText="Click Here!"
22+
ctaUrl="https://google.com"
23+
/>
24+
</Story>
25+
</Canvas>

components/molecules/Card/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {default} from './Card.js'

pages/blog/[[...slug]].js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import postComment from '@/api/frontend/wp/comments/postComment'
33
import getPostTypeStaticPaths from '@/api/wordpress/_global/getPostTypeStaticPaths'
44
import getPostTypeStaticProps from '@/api/wordpress/_global/getPostTypeStaticProps'
55
import Breadcrumbs from '@/components/atoms/Breadcrumbs'
6+
import Button from '@/components/atoms/Button'
67
import Container from '@/components/atoms/Container'
78
import Text from '@/components/atoms/Inputs/Text'
89
import Layout from '@/components/common/Layout'
910
import Blocks from '@/components/molecules/Blocks'
11+
import Card from '@/components/molecules/Card'
1012
import Form from '@/components/molecules/Form'
1113
import getPagePropTypes from '@/functions/getPagePropTypes'
12-
import Link from 'next/link'
1314
import * as Yup from 'yup'
1415

1516
// Define route post type.
@@ -41,26 +42,26 @@ export default function BlogPost({post, archive, posts, pagination}) {
4142
return (
4243
<Layout seo={{...post?.seo}}>
4344
<Container>
44-
<section>
45-
{!posts || !posts.length ? (
46-
<p>No posts found.</p>
47-
) : (
48-
posts.map((post, index) => (
49-
<article key={index}>
50-
<Link href={post?.uri}>
51-
<a>
52-
<h1 dangerouslySetInnerHTML={{__html: post?.title}} />
53-
</a>
54-
</Link>
55-
<div dangerouslySetInnerHTML={{__html: post?.excerpt}} />
56-
</article>
57-
))
58-
)}
59-
{/* TODO: replace this with a component. */}
60-
<button onClick={loadPosts} disabled={!pagination.hasNextPage}>
61-
Load more
62-
</button>
63-
</section>
45+
{!posts || !posts.length ? (
46+
<p>No posts found.</p>
47+
) : (
48+
<div className="w-1/3 grid grid-cols-1 gap-12">
49+
{posts.map((post, index) => (
50+
<Card
51+
key={index}
52+
title={post?.title}
53+
url={post?.uri}
54+
body={post?.excerpt}
55+
/>
56+
))}
57+
</div>
58+
)}
59+
<Button
60+
onClick={() => loadPosts}
61+
text="Load More"
62+
type="secondary"
63+
disabled={!pagination.hasNextPage}
64+
/>
6465
</Container>
6566
</Layout>
6667
)

pages/team/[[...slug]].js

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import getArchivePosts from '@/api/frontend/wp/archive/getArchivePosts'
22
import getPostTypeStaticPaths from '@/api/wordpress/_global/getPostTypeStaticPaths'
33
import getPostTypeStaticProps from '@/api/wordpress/_global/getPostTypeStaticProps'
44
import Breadcrumbs from '@/components/atoms/Breadcrumbs'
5+
import Button from '@/components/atoms/Button'
56
import Container from '@/components/atoms/Container'
67
import Layout from '@/components/common/Layout'
78
import Blocks from '@/components/molecules/Blocks'
9+
import Card from '@/components/molecules/Card'
810
import getPagePropTypes from '@/functions/getPagePropTypes'
9-
import Link from 'next/link'
1011

1112
// Define route post type.
1213
const postType = 'team'
@@ -36,25 +37,27 @@ export default function Team({post, archive, posts, pagination}) {
3637
if (archive) {
3738
return (
3839
<Layout seo={{...post?.seo}}>
39-
<Container>
40+
<Container className="container py-20">
4041
{!posts || !posts.length ? (
4142
<p>No posts found.</p>
4243
) : (
43-
posts.map((post, index) => (
44-
<article key={index}>
45-
<Link href={post.uri}>
46-
<a>
47-
<h1 dangerouslySetInnerHTML={{__html: post?.title}} />
48-
</a>
49-
</Link>
50-
<div dangerouslySetInnerHTML={{__html: post?.excerpt}} />
51-
</article>
52-
))
44+
<div className="w-1/3 grid grid-cols-1 gap-12">
45+
{posts.map((post, index) => (
46+
<Card
47+
key={index}
48+
title={post?.title}
49+
url={post?.uri}
50+
body={post?.excerpt}
51+
/>
52+
))}
53+
</div>
5354
)}
54-
{/* TODO: replace this with a component. */}
55-
<button onClick={loadPosts} disabled={!pagination.hasNextPage}>
56-
Load more
57-
</button>
55+
<Button
56+
onClick={() => loadPosts}
57+
text="Load More"
58+
type="secondary"
59+
disabled={!pagination.hasNextPage}
60+
/>
5861
</Container>
5962
</Layout>
6063
)

0 commit comments

Comments
 (0)