Skip to content

Commit 58d7798

Browse files
authored
add book list component for flexpages (#2736)
* add book list component for flexpages * 👕 * 👕 * add spec * fix type
1 parent 0aa7725 commit 58d7798

File tree

9 files changed

+169
-49
lines changed

9 files changed

+169
-49
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react';
2+
import PromoteBadge from '~/components/promote-badge/promote-badge';
3+
import type {Item} from '~/models/book-titles';
4+
import {Book as BookInfo} from '~/pages/subjects/new/specific/context';
5+
import GetTheBookDropdown from './dropdown-menu';
6+
import cn from 'classnames';
7+
import './book-tile.scss';
8+
9+
type AdditionalFields = Partial<{
10+
promoteSnippet: Item['promote_snippet'];
11+
bookState: string;
12+
}>
13+
14+
export default function BookTile({book}: {book: BookInfo & AdditionalFields}) {
15+
const {coverUrl, title, slug} = book;
16+
const comingSoon = book.bookState === 'coming_soon';
17+
const snippets = book.promoteSnippet?.filter((s) => s.value.image);
18+
const promoteSnippet = snippets?.find((s) => s.value.image);
19+
const classes = cn({
20+
'book-tile': true,
21+
'coming-soon': comingSoon,
22+
promote: Boolean(promoteSnippet)
23+
});
24+
25+
return (
26+
<div className={classes}>
27+
<a href={`/details/${slug}`} aria-label={`${title} book`}>
28+
<img
29+
src={coverUrl}
30+
role='presentation'
31+
width='240'
32+
height='240'
33+
/>
34+
{promoteSnippet?.value.image && (
35+
<PromoteBadge
36+
name={promoteSnippet.value.name}
37+
image={promoteSnippet.value.image}
38+
/>
39+
)}
40+
<div className='text-block'>
41+
{title}
42+
</div>
43+
</a>
44+
{comingSoon ? (
45+
<div className='navmenu'>
46+
<button type='button' disabled>
47+
Coming soon
48+
</button>
49+
</div>
50+
) : (
51+
<GetTheBookDropdown bookInfo={book} />
52+
)}
53+
</div>
54+
);
55+
}

src/app/components/book-tile/book-tile.tsx

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,19 @@
11
import React from 'react';
2-
import PromoteBadge from '~/components/promote-badge/promote-badge';
32
import bookPromise, {Item} from '~/models/book-titles';
43
import {Book as BookInfo} from '~/pages/subjects/new/specific/context';
5-
import GetTheBookDropdown from './dropdown-menu';
6-
import cn from 'classnames';
74
import './book-tile.scss';
5+
import BookTileDisplay from './book-tile-display';
86

9-
// eslint-disable-next-line complexity
107
export default function BookTile({book: [book]}: {book: [BookInfo]}) {
11-
const {coverUrl, title, slug} = book;
128
const info = useBookInfo(book.id);
13-
const comingSoon = info?.book_state === 'coming_soon';
14-
const snippets = info?.promote_snippet.filter((s) => s.value.image);
15-
const promoteSnippet = snippets?.find((s) => s.value.image);
16-
const classes = cn({
17-
'book-tile': true,
18-
'coming-soon': comingSoon,
19-
promote: Boolean(promoteSnippet)
20-
});
219

22-
return (
23-
<div className={classes}>
24-
<a href={`/details/${slug}`} aria-label={`${title} book`}>
25-
<img
26-
src={coverUrl}
27-
role='presentation'
28-
width='240'
29-
height='240'
30-
/>
31-
{promoteSnippet?.value.image && (
32-
<PromoteBadge
33-
name={promoteSnippet.value.name}
34-
image={promoteSnippet.value.image}
35-
/>
36-
)}
37-
<div className='text-block'>
38-
{title}
39-
</div>
40-
</a>
41-
{comingSoon ? (
42-
<div className='navmenu'>
43-
<button type='button' disabled>
44-
Coming soon
45-
</button>
46-
</div>
47-
) : (
48-
<GetTheBookDropdown bookInfo={book} />
49-
)}
50-
</div>
51-
);
10+
return <BookTileDisplay
11+
book={{
12+
...book,
13+
bookState: info?.book_state,
14+
promoteSnippet: info?.promote_snippet
15+
}}
16+
/>;
5217
}
5318

5419
function useBookInfo(id: number) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@import 'pattern-library/core/pattern-library/headers';
2+
3+
.flex-page.page div.content-block-book-list {
4+
display: grid;
5+
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
6+
margin: $normal-margin 0;
7+
width: 100%;
8+
9+
@include width-up-to($phone-max) {
10+
grid-gap: 1rem;
11+
}
12+
13+
@include wider-than($phone-max) {
14+
grid-gap: 4rem 2rem;
15+
}
16+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import cn from 'classnames';
3+
import './BookListBlock.scss';
4+
import BookTile from '~/components/book-tile/book-tile-display';
5+
6+
/*
7+
* the book data formatting in the CMS is currently fragemented,
8+
* when it gets centralized we can centralize the types as well
9+
*/
10+
export type BookInfo = {
11+
id: number;
12+
slug: string;
13+
title: string;
14+
webviewRexLink: string;
15+
webviewLink: string;
16+
highResolutionPdfUrl: string;
17+
lowResolutionPdfUrl: string;
18+
coverUrl: string;
19+
bookState: string;
20+
promoteSnippet: {
21+
value: {
22+
id: number;
23+
description: string;
24+
image: string;
25+
name: string;
26+
};
27+
}[];
28+
};
29+
30+
export type BookListBlockConfig = {
31+
id: string;
32+
type: 'book_list';
33+
value: {
34+
books: BookInfo[];
35+
};
36+
};
37+
38+
export function BookListBlock({data}: {data: BookListBlockConfig}) {
39+
return (
40+
<div className={cn('content-block-book-list')}>
41+
{data.value.books.map((book) => <BookTile key={book.id} book={book} />)}
42+
</div>
43+
);
44+
}

src/app/pages/flex-page/blocks/ContentBlock.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { HTMLBlockConfig, HTMLBlock } from './HTMLBlock';
99
import { LinksBlockConfig, LinksBlock } from './LinksBlock';
1010
import { QuoteBlock, QuoteBlockConfig } from './QuoteBlock';
1111
import { FAQBlockConfig, FAQBlock } from './FAQBlock';
12+
import { BookListBlockConfig, BookListBlock } from './BookListBlock';
1213

1314
export type ContentBlockConfig =
1415
LinksBlockConfig |
@@ -20,6 +21,7 @@ export type ContentBlockConfig =
2021
RichTextBlockConfig |
2122
QuoteBlockConfig |
2223
FAQBlockConfig |
24+
BookListBlockConfig |
2325
CardsBlockConfig;
2426

2527
export function ContentBlocks({data}: {data: ContentBlockConfig[]}) {
@@ -51,6 +53,8 @@ export function ContentBlock({data}: {data: ContentBlockConfig}) {
5153
return <QuoteBlock data={data} />;
5254
case 'faq':
5355
return <FAQBlock data={data} />;
56+
case 'book_list':
57+
return <BookListBlock data={data} />;
5458
default:
5559
return <pre>{JSON.stringify(data, null, 2)}</pre>;
5660
}

src/app/pages/flex-page/blocks/RichTextBlock.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
width: 100%;
1010
height: auto;
1111
}
12-
12+
1313
img.richtext-image.left {
1414
float: left;
1515
margin-right: $normal-margin;
1616
}
17-
17+
1818
img.richtext-image.right {
1919
float: right;
2020
margin-left: $normal-margin;
@@ -23,7 +23,7 @@
2323
> *:first-child {
2424
margin-top: 0;
2525
}
26-
> *:last-child {
26+
> *:not(h1, h2, h3, h4, h5, h6):last-child {
2727
margin-bottom: 0;
2828
}
2929

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
.page.flex-page {
3+
overflow: visible;
4+
}

src/app/pages/flex-page/flex-page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { LoadedPage } from '~/components/jsx-helpers/loader-page';
33
import { ContentBlocks, ContentBlockConfig } from './blocks/ContentBlock';
4+
import './flex-page.scss';
45

56
type Data = {
67
body: ContentBlockConfig[];

test/src/pages/flex-page.test.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import ShellContextProvider from '~/../../test/helpers/shell-context';
23
import {render, screen} from '@testing-library/preact';
34
import {describe, it} from '@jest/globals';
45
import userEvent from '@testing-library/user-event';
@@ -38,9 +39,11 @@ let body: Data['body'];
3839

3940
function Component() {
4041
return (
41-
<MemoryRouter initialEntries={['']}>
42-
<FlexPage data={{body}} />
43-
</MemoryRouter>
42+
<ShellContextProvider>
43+
<MemoryRouter initialEntries={['']}>
44+
<FlexPage data={{body}} />
45+
</MemoryRouter>
46+
</ShellContextProvider>
4447
);
4548
}
4649

@@ -156,6 +159,11 @@ describe('flex-page', () => {
156159
expect(screen.getAllByText('Some text with')).toHaveLength(1);
157160
expect(screen.getAllByText('formatting')).toHaveLength(1);
158161
});
162+
it('renders bookListBlock', () => {
163+
body = [bookListBlock()];
164+
render(<Component />);
165+
expect(screen.getAllByText('book title')).toHaveLength(1);
166+
});
159167
});
160168

161169
function imageBlock(name: string) {
@@ -338,3 +346,26 @@ function sectionBlock(): ContentBlockConfig {
338346
}
339347
};
340348
}
349+
350+
function bookListBlock(): ContentBlockConfig {
351+
return {
352+
id: 'book-list-id',
353+
type: 'book_list',
354+
value: {
355+
books: [
356+
{
357+
id: 1,
358+
slug: 'book-slug',
359+
title: 'book title',
360+
webviewRexLink: 'webview-rex-link',
361+
webviewLink: 'webview-link',
362+
highResolutionPdfUrl: 'high-res-url',
363+
lowResolutionPdfUrl: 'low-res-url',
364+
coverUrl: 'cover-url',
365+
bookState: 'book-state',
366+
promoteSnippet: [],
367+
}
368+
]
369+
}
370+
};
371+
}

0 commit comments

Comments
 (0)