Skip to content

Commit 8fb2095

Browse files
committed
feat: support mixed indent type lists
1 parent 4d40e76 commit 8fb2095

10 files changed

Lines changed: 195 additions & 137 deletions

File tree

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v20
1+
v22

README.md

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ npm install @sensinum/astro-strapi-blocks@latest
5151
- 📌 Headers (H1 - H6)
5252
- 📝 Paragraph with formatting (italic, bold, underline, strikethrough, link)
5353
- 📑 Quote with formatting (italic, bold, underline, strikethrough, link)
54-
- 📋 List (ordered and unordered)
54+
- 📋 List (ordered and unordered, nested lists with per-level `indent` theme)
5555
- 💻 Code blocks
5656
- 🖼️ Image blocks
5757
- 🎨 Flexible block class configuration for custom styling
@@ -164,6 +164,10 @@ type StrapiBlockUserTheme = {
164164
unordered?: string[];
165165
item?: string[];
166166
nested?: string[];
167+
indent?: {
168+
ordered?: string[];
169+
unordered?: string[];
170+
};
167171
};
168172
code?: {
169173
block?: string[];
@@ -226,10 +230,18 @@ const StrapiBlockThemeDefault = {
226230
},
227231
list: {
228232
block: ['astro-strapi-block-list', 'my-4'],
229-
ordered: ['list-decimal', 'pl-6'],
230-
unordered: ['list-disc', 'pl-6'],
233+
ordered: ['pl-6'],
234+
unordered: ['pl-6'],
231235
item: ['mb-2', 'last:mb-0'],
232-
nested: ['mb-2']
236+
nested: ['mb-2'],
237+
indent: {
238+
ordered: [
239+
'list-decimal', 'list-[lower-latin]', 'list-[lower-roman]', 'list-[upper-latin]', 'list-[upper-roman]', 'list-decimal',
240+
],
241+
unordered: [
242+
'list-disc', 'list-[circle]', 'list-[square]', 'list-disk', 'list-[circle]', 'list-[square]',
243+
],
244+
},
233245
},
234246
code: {
235247
block: ['astro-strapi-block-code', 'mb-4', 'bg-gray-200', 'p-4', 'rounded-md', 'text-sm', 'font-mono', 'last:mb-0'],
@@ -245,6 +257,28 @@ const StrapiBlockThemeDefault = {
245257

246258
This default theme provides a clean, modern look using Tailwind CSS classes. You can use this as a starting point for your custom themes.
247259

260+
#### Lists: nested lists and `indent`
261+
262+
List styling is split between the base list (`list.ordered` / `list.unordered`), list items (`list.item`), optional spacing when the tree contains sublists (`list.nested`), and **per–nesting-level** marker classes (`list.indent`).
263+
264+
- **`list.indent.ordered`** and **`list.indent.unordered`** are **arrays of class strings**: index `0` is used for the top-level list, index `1` for the first nested list, and so on. The renderer uses Strapi’s optional `indentLevel` on a `list` node so that, when a child list is flagged with `indentLevel`, the depth is incremented for that subtree. If the index is out of range, no extra indent class is applied for that level.
265+
- The theme helpers support a **three-segment** path for this branch: `getPropertyClass(theme, ['list', 'indent', 'ordered' | 'unordered'])` returns that array, and you can pick a single level with `[indentLevel]`. The two-segment `renderPropertyClasses(theme, ['list', 'ordered' | 'unordered'])` (and the same for `item`, `nested`, etc.) is unchanged. Using `renderPropertyClasses(theme, ['list', 'indent', format])` **joins the entire** `indent` array into one class string, which is appropriate only if you want all marker utilities on one element; for per-level markers, use `getPropertyClass` and index as above.
266+
267+
Example — extend only nested marker styles (Tailwind list-style steps):
268+
269+
```typescript
270+
theme={{
271+
extend: {
272+
list: {
273+
indent: {
274+
ordered: ['list-decimal', 'list-[lower-alpha]', 'list-[lower-roman]'],
275+
unordered: ['list-disc', 'list-[circle]', 'list-[square]'],
276+
},
277+
},
278+
},
279+
}}
280+
```
281+
248282
#### Examples
249283

250284
1. Extending default theme:
@@ -372,14 +406,16 @@ type QuoteBlockProps = {
372406
##### List Block
373407
```typescript
374408
type ListBlockProps = {
375-
data: Array<StrapiBlockNode>; // List items
376-
class?: string; // Additional CSS classes
377-
theme: StrapiBlockTheme; // Theme configuration
409+
data: Array<StrapiBlockListItem | StrapiBlockList>; // List items and nested lists
410+
class?: string; // Additional CSS classes
411+
theme: StrapiBlockTheme; // Theme configuration
378412
format: 'ordered' | 'unordered'; // List type
379-
nested: boolean; // Is the list nested?
413+
nested?: boolean; // True when the tree contains sublists
380414
}
381415
```
382416
417+
`StrapiBlockList` nodes in `data` may include an optional `indentLevel` field from Strapi; the default list component uses it together with `theme.list.indent` to choose list-style classes by nesting depth. Import `StrapiBlockListItem` and `StrapiBlockList` from the package types when you type custom list blocks.
418+
383419
##### Code Block
384420
```typescript
385421
type CodeBlockProps = {

jest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
export default {
33
preset: 'ts-jest',
44
testEnvironment: 'node',
5+
// Only .ts / .tsx — plain .js tests are not passed through ts-jest and ESM in them will fail
6+
testMatch: ['**/?(*.)+(test).ts', '**/?(*.)+(test).tsx'],
57
moduleNameMapper: {
68
'^@/(.*)$': '<rootDir>/src/$1'
79
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sensinum/astro-strapi-blocks",
3-
"version": "1.3.0",
3+
"version": "1.3.1",
44
"description": "Astro components for Strapi Block Field",
55
"keywords": [
66
"astro",
Lines changed: 13 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
---
2-
import ListItem from "./items/StrapiBlockListItem.astro";
3-
import List from "./items/StrapiBlockList.astro";
42
import StrapiBlockCustom from "./StrapiBlockCustom.astro";
3+
import StrapiListNested from "./items/StrapiBlockListNested.astro";
54
6-
import { renderPropertyClasses, StrapiBlockThemeDefault } from "../../lib";
5+
import { StrapiBlockThemeDefault } from "../../lib";
76
import type {
87
AstroComponent,
98
StrapiBlockListItem,
109
StrapiBlockList,
1110
StrapiBlockListType,
1211
StrapiBlockTheme,
12+
StrapiBlockNode,
1313
} from "../../types";
1414
1515
type Props<FontColors extends string> = {
1616
class?: string;
1717
format: StrapiBlockListType;
18-
nested: boolean;
18+
nested?: boolean;
1919
color?: FontColors;
20-
data: Array<StrapiBlockListItem>;
20+
data: Array<StrapiBlockListItem | StrapiBlockList>;
2121
block?: AstroComponent;
2222
theme: StrapiBlockTheme;
2323
};
@@ -34,80 +34,20 @@ const {
3434
const renderData = data.filter(
3535
(item: StrapiBlockListItem) => item.children && item.children.length,
3636
);
37-
const nestedClasses = nested
38-
? renderPropertyClasses(theme, ["list", "nested"])
39-
: "";
37+
const hasNestedList = renderData.filter((item: StrapiBlockListItem | StrapiBlockList) => item.type === "list").length > 0;
4038
---
4139

4240
{block && <StrapiBlockCustom {...Astro.props} comp={block} data={renderData} />}
4341
{
4442
!block && (
4543
<div class={classes}>
46-
{format === "unordered" && (
47-
<List
48-
tag="ul"
49-
class={[
50-
renderPropertyClasses(theme, ["list", "unordered"]),
51-
nestedClasses,
52-
]
53-
.filter(Boolean)
54-
.join(" ")}
55-
>
56-
{renderData.map(
57-
(
58-
item: StrapiBlockListItem | StrapiBlockList,
59-
index: number,
60-
) => (
61-
<ListItem
62-
no={index + 1}
63-
class={renderPropertyClasses(theme, [
64-
"list",
65-
"item",
66-
])}
67-
data={item.children}
68-
parentFormat={
69-
item.type === "list" ? item.format : format
70-
}
71-
nested={item.type === "list"}
72-
theme={theme}
73-
/>
74-
),
75-
)}
76-
</List>
77-
)}
78-
79-
{format === "ordered" && (
80-
<List
81-
tag="ol"
82-
class={[
83-
renderPropertyClasses(theme, ["list", "ordered"]),
84-
nestedClasses,
85-
]
86-
.filter(Boolean)
87-
.join(" ")}
88-
>
89-
{renderData.map(
90-
(
91-
item: StrapiBlockListItem | StrapiBlockList,
92-
index: number,
93-
) => (
94-
<ListItem
95-
no={index + 1}
96-
class={renderPropertyClasses(theme, [
97-
"list",
98-
"item",
99-
])}
100-
data={item.children}
101-
parentFormat={
102-
item.type === "list" ? item.format : format
103-
}
104-
nested={item.type === "list"}
105-
theme={theme}
106-
/>
107-
),
108-
)}
109-
</List>
110-
)}
44+
<StrapiListNested
45+
data={renderData}
46+
format={format}
47+
nested={nested || hasNestedList}
48+
theme={theme}
49+
nestedListComponent={StrapiListNested}
50+
/>
11151
</div>
11252
)
11353
}
Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
---
22
import SanitizedNode from "./StrapiBlockNode.astro";
3-
import StrapiList from "../StrapiBlockList.astro";
4-
import type {
5-
StrapiBlockListItem,
6-
StrapiBlockList,
7-
StrapiBlockNode,
8-
StrapiBlockTheme,
9-
StrapiBlockListType,
10-
} from "../../../types";
3+
import type { StrapiBlockNode, StrapiBlockTheme } from "../../../types";
114
import { sanitizeStrapiNodeData, StrapiBlockThemeDefault } from "../../../lib";
125
type Props = {
136
class?: string;
147
no: number;
15-
parentFormat: StrapiBlockListType;
16-
nested: boolean;
178
data: Array<StrapiBlockNode>;
189
theme: StrapiBlockTheme;
1910
};
@@ -23,23 +14,10 @@ const {
2314
data,
2415
class: classes = "",
2516
theme = StrapiBlockThemeDefault,
26-
parentFormat,
27-
nested,
2817
} = Astro.props;
2918
3019
const sanitizedData = sanitizeStrapiNodeData(data);
3120
const hasTextContent = sanitizedData.length > 0;
32-
const nestedListItems = data.filter(
33-
(item: StrapiBlockNode) => item.type === "list-item",
34-
);
35-
const nestedList =
36-
nestedListItems.length > 0
37-
? ({
38-
type: "list",
39-
format: parentFormat,
40-
children: nestedListItems,
41-
} as StrapiBlockList)
42-
: null;
4321
---
4422

4523
{
@@ -49,19 +27,3 @@ const nestedList =
4927
</li>
5028
)
5129
}
52-
{
53-
nestedList && (
54-
<>
55-
{ hasTextContent && (<li class={classes}>
56-
<SanitizedNode data={sanitizedData} theme={theme.paragraph} />
57-
</li>
58-
)}
59-
<StrapiList
60-
data={nestedList.children}
61-
format={nestedList.format}
62-
nested={nested}
63-
theme={theme}
64-
/>
65-
</>
66-
)
67-
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
import ListItem from "./StrapiBlockListItem.astro";
3+
import List from "./StrapiBlockList.astro";
4+
5+
import { getPropertyClass, renderPropertyClasses, StrapiBlockThemeDefault } from "../../../lib";
6+
import type {
7+
AstroComponent,
8+
StrapiBlockListItem,
9+
StrapiBlockList,
10+
StrapiBlockListType,
11+
StrapiBlockTheme,
12+
} from "../../../types";
13+
14+
type Props = {
15+
class?: string;
16+
tag?: "ul" | "ol";
17+
format: StrapiBlockListType;
18+
indentLevel?: number;
19+
nested: boolean;
20+
data: Array<StrapiBlockListItem | StrapiBlockList>;
21+
theme: StrapiBlockTheme;
22+
nestedListComponent?: AstroComponent;
23+
};
24+
25+
const {
26+
data,
27+
format = "unordered",
28+
class: classes = "",
29+
indentLevel = 0,
30+
theme = StrapiBlockThemeDefault,
31+
nested,
32+
nestedListComponent,
33+
} = Astro.props;
34+
35+
const nestedClasses = nested
36+
? renderPropertyClasses(theme, ["list", "nested"])
37+
: "";
38+
const listTag = format === "unordered" ? "ul" : "ol";
39+
const indentClasses = getPropertyClass(theme, ["list", "indent", format])[indentLevel] || "";
40+
const NestedListComponent = nestedListComponent || null;
41+
---
42+
43+
<List
44+
tag={listTag}
45+
class={[
46+
renderPropertyClasses(theme, ["list", format]),
47+
nestedClasses,
48+
indentClasses,
49+
]
50+
.filter(Boolean)
51+
.join(" ")}
52+
>
53+
{
54+
data.map(
55+
(item: StrapiBlockListItem | StrapiBlockList, index: number) => (
56+
<>
57+
{item.type === "list-item" && (
58+
<ListItem
59+
no={index + 1}
60+
class={renderPropertyClasses(theme, [
61+
"list",
62+
"item",
63+
])}
64+
data={item.children}
65+
theme={theme}
66+
/>
67+
)}
68+
{item.type === "list" &&
69+
NestedListComponent &&
70+
item.children &&
71+
item.children.length > 0 && (
72+
<NestedListComponent
73+
data={item.children}
74+
format={item.format}
75+
indentLevel={item.indentLevel ? indentLevel + 1 : indentLevel}
76+
nested={
77+
item.children.filter(
78+
(
79+
item:
80+
| StrapiBlockListItem
81+
| StrapiBlockList,
82+
) => item.type === "list",
83+
).length > 0
84+
}
85+
theme={theme}
86+
nestedListComponent={NestedListComponent}
87+
/>
88+
)}
89+
</>
90+
),
91+
)
92+
}
93+
</List>

0 commit comments

Comments
 (0)