Skip to content

Commit c6f8927

Browse files
Merge pull request #31 from moveyourdigital/issue-30-nested-list
Support NestedList
2 parents 5cde74a + 61480c9 commit c6f8927

File tree

4 files changed

+295
-21
lines changed

4 files changed

+295
-21
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ The package ships with the following renderers, but you can include your custom
5353
- SimpleImage
5454
- Embed
5555
- List
56+
- NestedList
5657
- Table
5758
- Quote
5859
- Delimiter

src/renderers/list/__snapshots__/index.test.tsx.snap

+211-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`<List /> when receives a list ordered block renders a <ol> block 1`] = `
3+
exports[`<List /> when receives a list "ordered" block renders a <ol> block 1`] = `
44
<ol>
55
<li>
66
It is a block-styled editor
77
</li>
88
<li>
9-
It returns clean data output in JSON
9+
It returns clean
10+
<b>
11+
data output
12+
</b>
13+
in JSON
1014
</li>
1115
<li>
1216
Designed to be extendable and pluggable with a simple API
1317
</li>
1418
</ol>
1519
`;
1620
17-
exports[`<List /> when receives a list unordered block renders a <ul> block 1`] = `
21+
exports[`<List /> when receives a list "unordered" block renders a <ul> block 1`] = `
1822
<ul>
1923
<li>
2024
It is a block-styled editor
@@ -31,3 +35,207 @@ exports[`<List /> when receives a list unordered block renders a <ul> block 1`]
3135
</li>
3236
</ul>
3337
`;
38+
39+
exports[`<List /> when receives a nested list "ordered" block and when className is provided renders className to all <ol> blocks 1`] = `
40+
<ol
41+
className="list"
42+
>
43+
<li>
44+
It is a block-styled editor
45+
</li>
46+
<li>
47+
It returns clean data output in JSON
48+
<ol
49+
className="list"
50+
>
51+
<li>
52+
Designed to be extendable and pluggable with a simple API
53+
<ol
54+
className="list"
55+
>
56+
<li>
57+
Red
58+
</li>
59+
<li>
60+
Green
61+
</li>
62+
</ol>
63+
</li>
64+
</ol>
65+
</li>
66+
<li>
67+
It returns clean data output in JSON
68+
</li>
69+
<li>
70+
Designed to be extendable and pluggable with a simple API
71+
</li>
72+
</ol>
73+
`;
74+
75+
exports[`<List /> when receives a nested list "ordered" block and when className is provided renders className to all <ol> blocks 2`] = `
76+
<ol
77+
className="list-ul px-3 py-2"
78+
>
79+
<li>
80+
It is a block-styled editor
81+
</li>
82+
<li>
83+
It returns clean data output in JSON
84+
<ol
85+
className="list-ul px-3 py-2"
86+
>
87+
<li>
88+
Designed to be extendable and pluggable with a simple API
89+
<ol
90+
className="list-ul px-3 py-2"
91+
>
92+
<li>
93+
Red
94+
</li>
95+
<li>
96+
Green
97+
</li>
98+
</ol>
99+
</li>
100+
</ol>
101+
</li>
102+
<li>
103+
It returns clean data output in JSON
104+
</li>
105+
<li>
106+
Designed to be extendable and pluggable with a simple API
107+
</li>
108+
</ol>
109+
`;
110+
111+
exports[`<List /> when receives a nested list "ordered" block renders a <ol> block 1`] = `
112+
<ol>
113+
<li>
114+
It is a block-styled editor
115+
</li>
116+
<li>
117+
It returns clean data output in JSON
118+
<ol>
119+
<li>
120+
Designed to be extendable and pluggable with a simple API
121+
<ol>
122+
<li>
123+
Red
124+
</li>
125+
<li>
126+
Green
127+
</li>
128+
</ol>
129+
</li>
130+
</ol>
131+
</li>
132+
<li>
133+
It returns clean data output in JSON
134+
</li>
135+
<li>
136+
Designed to be extendable and pluggable with a simple API
137+
</li>
138+
</ol>
139+
`;
140+
141+
exports[`<List /> when receives a nested list "unordered" block and when className is provided renders className to all <ul> blocks 1`] = `
142+
<ul
143+
className="list"
144+
>
145+
<li>
146+
It is a block-styled editor
147+
</li>
148+
<li>
149+
It returns clean data output in JSON
150+
<ul
151+
className="list"
152+
>
153+
<li>
154+
Designed to be extendable and pluggable with a simple API
155+
<ul
156+
className="list"
157+
>
158+
<li>
159+
Red
160+
</li>
161+
<li>
162+
Green
163+
</li>
164+
</ul>
165+
</li>
166+
</ul>
167+
</li>
168+
<li>
169+
It returns clean data output in JSON
170+
</li>
171+
<li>
172+
Designed to be extendable and pluggable with a simple API
173+
</li>
174+
</ul>
175+
`;
176+
177+
exports[`<List /> when receives a nested list "unordered" block and when className is provided renders className to all <ul> blocks 2`] = `
178+
<ul
179+
className="list-ul px-3 py-2"
180+
>
181+
<li>
182+
It is a block-styled editor
183+
</li>
184+
<li>
185+
It returns clean data output in JSON
186+
<ul
187+
className="list-ul px-3 py-2"
188+
>
189+
<li>
190+
Designed to be extendable and pluggable with a simple API
191+
<ul
192+
className="list-ul px-3 py-2"
193+
>
194+
<li>
195+
Red
196+
</li>
197+
<li>
198+
Green
199+
</li>
200+
</ul>
201+
</li>
202+
</ul>
203+
</li>
204+
<li>
205+
It returns clean data output in JSON
206+
</li>
207+
<li>
208+
Designed to be extendable and pluggable with a simple API
209+
</li>
210+
</ul>
211+
`;
212+
213+
exports[`<List /> when receives a nested list "unordered" block renders a <ul> block 1`] = `
214+
<ul>
215+
<li>
216+
It is a block-styled editor
217+
</li>
218+
<li>
219+
It returns clean data output in JSON
220+
<ul>
221+
<li>
222+
Designed to be extendable and pluggable with a simple API
223+
<ul>
224+
<li>
225+
Red
226+
</li>
227+
<li>
228+
Green
229+
</li>
230+
</ul>
231+
</li>
232+
</ul>
233+
</li>
234+
<li>
235+
It returns clean data output in JSON
236+
</li>
237+
<li>
238+
Designed to be extendable and pluggable with a simple API
239+
</li>
240+
</ul>
241+
`;

src/renderers/list/index.test.tsx

+50-9
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,74 @@ import { create } from 'react-test-renderer';
33
import List, { ListBlockData } from '.';
44

55
describe('<List />', () => {
6-
describe('when receives a list unordered block', () => {
6+
describe.each([
7+
['unordered', '<ul>'],
8+
['ordered', '<ol>'],
9+
])('when receives a list %p block', (style, tag) => {
710
const data: ListBlockData = {
8-
style: 'unordered',
11+
// @ts-expect-error
12+
style,
913
items: [
1014
'It is a block-styled editor',
1115
'It returns clean <b>data output</b> in JSON',
1216
'Designed to be extendable and pluggable with a simple API',
1317
],
1418
};
1519

16-
it('renders a <ul> block', () => {
20+
it(`renders a ${tag} block`, () => {
1721
expect(create(<List data={data} />).toJSON()).toMatchSnapshot();
1822
});
1923
});
2024

21-
describe('when receives a list ordered block', () => {
25+
describe.each([
26+
['unordered', '<ul>'],
27+
['ordered', '<ol>'],
28+
])('when receives a nested list %p block', (style, tag) => {
2229
const data: ListBlockData = {
23-
style: 'ordered',
30+
// @ts-expect-error
31+
style,
2432
items: [
25-
'It is a block-styled editor',
26-
'It returns clean data output in JSON',
27-
'Designed to be extendable and pluggable with a simple API',
33+
{
34+
content: 'It is a block-styled editor',
35+
items: [],
36+
},
37+
{
38+
content: 'It returns clean data output in JSON',
39+
items: [
40+
{
41+
content: 'Designed to be extendable and pluggable with a simple API',
42+
items: [
43+
{
44+
content: 'Red',
45+
items: [],
46+
},
47+
{
48+
content: 'Green',
49+
items: [],
50+
},
51+
],
52+
},
53+
],
54+
},
55+
{
56+
content: 'It returns clean data output in JSON',
57+
items: [],
58+
},
59+
{
60+
content: 'Designed to be extendable and pluggable with a simple API',
61+
items: [],
62+
},
2863
],
2964
};
3065

31-
it('renders a <ol> block', () => {
66+
it(`renders a ${tag} block`, () => {
3267
expect(create(<List data={data} />).toJSON()).toMatchSnapshot();
3368
});
69+
70+
describe('and when className is provided', () => {
71+
it.each(['list', 'list-ul px-3 py-2'])(`renders className to all ${tag} blocks`, (className) => {
72+
expect(create(<List data={data} className={className} />).toJSON()).toMatchSnapshot();
73+
});
74+
});
3475
});
3576
});

src/renderers/list/index.tsx

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
1-
import React from 'react';
1+
import React, { FC, PropsWithChildren } from 'react';
22
import HTMLReactParser from 'html-react-parser';
33
import { RenderFn } from '../..';
44

55
export interface ListBlockData {
66
style: 'ordered' | 'unordered';
7-
items: string[];
7+
items: NestedListItem[];
88
}
99

10+
export type NestedListItem =
11+
| {
12+
content: string;
13+
items: NestedListItem[];
14+
}
15+
| string;
16+
17+
const Bullet: FC<PropsWithChildren<{}>> = ({ children }) => <li>{children}</li>;
18+
19+
const Group: FC<{
20+
Tag: keyof JSX.IntrinsicElements;
21+
items: NestedListItem[];
22+
className?: string;
23+
}> = ({ Tag, items, ...props }) => (
24+
<Tag {...props}>
25+
{items.map((item, i) => (
26+
<Bullet key={i}>
27+
{typeof item === 'string' ? (
28+
HTMLReactParser(item)
29+
) : (
30+
<>
31+
{HTMLReactParser(item?.content)}
32+
{item?.items?.length > 0 && <Group Tag={Tag} items={item.items} {...props} />}
33+
</>
34+
)}
35+
</Bullet>
36+
))}
37+
</Tag>
38+
);
39+
1040
const List: RenderFn<ListBlockData> = ({ data, className = '' }) => {
1141
const props: {
1242
[s: string]: string;
@@ -17,13 +47,7 @@ const List: RenderFn<ListBlockData> = ({ data, className = '' }) => {
1747
}
1848

1949
const Tag = (data?.style === 'ordered' ? `ol` : `ul`) as keyof JSX.IntrinsicElements;
20-
return (
21-
<Tag {...props}>
22-
{data?.items.map((item, i) => (
23-
<li key={i}>{HTMLReactParser(item)}</li>
24-
))}
25-
</Tag>
26-
);
50+
return data && <Group Tag={Tag} items={data.items} {...props} />;
2751
};
2852

2953
export default List;

0 commit comments

Comments
 (0)