Skip to content

Commit ad7825e

Browse files
authored
feat: implement card group (#8)
* feat: implement card-group mdx renderer * docs: create card documentation
1 parent 53019bd commit ad7825e

File tree

13 files changed

+570
-1
lines changed

13 files changed

+570
-1
lines changed

astro.config.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import rehypeMermaid from "rehype-mermaid";
1818
import rehypeCodeGroupReact from "./src/lib/plugins/code-group/plugin";
1919
import rehypeReadMoreReact from "./src/lib/plugins/read-more/plugin";
2020
import rehypeBlogListReact from "./src/lib/plugins/blog-list/plugin";
21+
import rehypeBlock from "./src/lib/plugins/parser/plugin";
2122
import {
2223
default as remarkDirective,
2324
default as remarkReadMoreDirective,
@@ -51,6 +52,7 @@ export default defineConfig({
5152
},
5253
remarkPlugins: [remarkDirective, remarkReadMoreDirective],
5354
rehypePlugins: [
55+
rehypeBlock,
5456
rehypeMermaid,
5557
[
5658
rehypeCallouts,

content/docs/documentation/foundamentals/components/_default.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ collection:
99
- code-block
1010
- markdown
1111
- text
12+
- card
1213
---
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
title: Card
3+
description: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4+
permalink: card
5+
icon: lucide:bell
6+
---
7+
8+
# Cards
9+
10+
This component allows you to group text elements while providing visual impact.
11+
12+
```
13+
:::card-group
14+
:::card {label="Test", icon="lucide:bell"}
15+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
16+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
17+
:::
18+
:::card {label="Test", icon="lucide:bell"}
19+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
20+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
21+
:::
22+
:::
23+
```
24+
25+
:::card-group {cols=2}
26+
:::card {label="Test", icon="lucide:bell"}
27+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
28+
:::
29+
:::card {label="Test", icon="lucide:bell"}
30+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
31+
:::
32+
:::
33+
34+
---
35+
36+
## Configuration
37+
38+
### Card per line
39+
40+
You can customize the number of elements per line by setting the `cols` prop.
41+
42+
> [!note]
43+
> Default card per line was fixed to 2
44+
45+
```
46+
:::card-group {cols=4}
47+
:::card {label="Test", icon="lucide:bell"}
48+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
49+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
50+
:::
51+
:::card {label="Test", icon="lucide:bell"}
52+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
53+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
54+
:::
55+
:::
56+
```
57+
58+
:::card-group {cols=4}
59+
:::card {label="Test", icon="lucide:bell"}
60+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
61+
:::
62+
:::card {label="Test", icon="lucide:bell"}
63+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
64+
:::
65+
:::
66+
67+
If the elements cannot be aligned horizontally, they will be moved to the next line.
68+
69+
```
70+
:::card-group {cols=2}
71+
:::card {label="Test", icon="lucide:bell"}
72+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
73+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
74+
:::
75+
:::card {label="Test", icon="lucide:bell"}
76+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
77+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
78+
:::
79+
:::card {label="Test", icon="lucide:bell"}
80+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
81+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
82+
:::
83+
:::
84+
```
85+
86+
:::card-group {cols=2}
87+
:::card {label="Test", icon="lucide:bell"}
88+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
89+
:::
90+
:::card {label="Test", icon="lucide:bell"}
91+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
92+
:::
93+
:::card {label="Test", icon="lucide:bell"}
94+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
95+
:::
96+
:::
97+
98+
### Card item
99+
100+
Vous pouvez personaliser chacune de vos cartes en utilisant les props suivantes :
101+
102+
- `label`: Le texte à afficher en haut de la carte. (required)
103+
- `icon`: L'icône à afficher en haut de la carte.
104+
105+
> [!note]
106+
> The cards use the [`iconify`](https://icon-sets.iconify.design/) library to display icons.
107+
108+
```
109+
:::card-group
110+
:::card {label="Test", icon="lucide:bell"}
111+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
112+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
113+
:::
114+
:::card {label="Test", icon="lucide:user"}
115+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
116+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
117+
:::
118+
:::
119+
```
120+
121+
:::card-group {cols=2}
122+
:::card {label="Test", icon="lucide:bell"}
123+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
124+
:::
125+
:::card {label="Test", icon="lucide:user"}
126+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
127+
:::
128+
:::

content/docs/documentation/getting-started/getting-started.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ icon: lucide:info
1010
## Introduction
1111

1212
Explainer provides a rich set of components that can be used directly in your Markdown files. This documentation outlines the various markdown components available for creating beautiful, interactive documentation.
13+
14+
:::card-group{cols=2}
15+
:::
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
type Props = {
3+
cols?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
4+
};
5+
6+
const { cols } = Astro.props;
7+
const gridCols = {
8+
1: "grid-cols-1",
9+
2: "grid-cols-2",
10+
3: "grid-cols-3",
11+
4: "grid-cols-4",
12+
5: "grid-cols-5",
13+
6: "grid-cols-6",
14+
7: "grid-cols-7",
15+
8: "grid-cols-8",
16+
9: "grid-cols-9",
17+
10: "grid-cols-10",
18+
11: "grid-cols-11",
19+
12: "grid-cols-12",
20+
};
21+
---
22+
23+
<div class:list={["grid gap-5 pt-5 pb-10", gridCols[cols ?? 2]]}>
24+
<slot />
25+
</div>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
import { Icon } from "@iconify/react";
3+
4+
type props = {
5+
label: string;
6+
icon?: string;
7+
};
8+
9+
const props = Astro.props;
10+
---
11+
12+
<div
13+
class="flex flex-col p-6 border rounded-xl group hover:bg-secondary w-full"
14+
>
15+
<div
16+
class="flex items-center justify-center w-8 h-8 rounded-full bg-secondary border group-hover:bg-primary/10 group-hover:border-primary/60 duration-150"
17+
>
18+
<Icon
19+
client:load
20+
icon={props.icon}
21+
className="group-hover:text-primary duration-150"
22+
/>
23+
</div>
24+
<div class="mt-4">
25+
<p class="font-semibold text-gray-900 !mt-0 !p-0">{props.label}</p>
26+
<p class="text-sm text-gray-500 !mt-0 !p-0">
27+
<slot />
28+
</p>
29+
</div>
30+
</div>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
const props = Astro.props;
3+
---
4+
5+
<p>
6+
<pre
7+
class="astro-code astro-code-themes github-light catppuccin-frappe has-highlighted"
8+
set:html={props.html}
9+
/>
10+
</p>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type { Root } from "unist";
2+
import { visit } from "unist-util-visit";
3+
import { CodeBlockSelector } from "./selector";
4+
5+
// Exemple de mapping startTag -> composant Astro
6+
const mdx: Record<string, any> = {
7+
"card-group": "CardGroup",
8+
card: "Card",
9+
codeblock: "Codeblock",
10+
};
11+
12+
interface Block {
13+
delimiter: string;
14+
startTag: string;
15+
attributes: Record<string, string>;
16+
children: Array<Block | { type: "text"; value: string }>;
17+
}
18+
19+
const parseAttributes = (str: string): Record<string, any> => {
20+
const regex = /(\w+)\s*=\s*(?:"([^"]*)"|(\S+))/g;
21+
const attrs: Record<string, any> = {};
22+
let match;
23+
24+
while ((match = regex.exec(str)) !== null) {
25+
const key = match[1];
26+
let value: any;
27+
28+
// Valeur entre guillemets
29+
if (match[2] !== undefined) {
30+
const raw = match[2];
31+
32+
// Essayer de parser JSON (array, object, number, boolean)
33+
try {
34+
value = JSON.parse(raw);
35+
} catch {
36+
value = raw; // fallback string
37+
}
38+
} else if (match[3] !== undefined) {
39+
// Valeur non-quoted (true, false, number, etc.)
40+
const raw = match[3];
41+
if (raw === "true") value = true;
42+
else if (raw === "false") value = false;
43+
else if (!isNaN(Number(raw))) value = Number(raw);
44+
else value = raw;
45+
}
46+
47+
attrs[key] = value;
48+
}
49+
50+
return attrs;
51+
};
52+
53+
const parseSingleNode = (node: { type: string; children?: any[] }): Block[] => {
54+
const parseChildren = (children: any[]): Block[] => {
55+
const blocks: Block[] = [];
56+
const stack: Block[] = [];
57+
58+
for (const child of children) {
59+
if (child.type === "text" || child.type === "mdxTextExpression") {
60+
const lines = child.value.split(/\r?\n/);
61+
for (const line of lines) {
62+
const startMatch = line.match(/^:::(\w[\w-]*)\s*(.*)$/);
63+
const endMatch = line.match(/^:::/);
64+
65+
if (startMatch) {
66+
const block: Block = {
67+
delimiter: ":::",
68+
startTag: startMatch[1],
69+
attributes: parseAttributes(startMatch[2] || ""),
70+
children: [],
71+
};
72+
stack.push(block);
73+
} else if (endMatch) {
74+
const finished = stack.pop();
75+
if (!finished) continue;
76+
if (stack.length > 0) {
77+
stack[stack.length - 1].children.push(finished);
78+
} else {
79+
blocks.push(finished);
80+
}
81+
} else {
82+
if (stack.length > 0) {
83+
stack[stack.length - 1].children.push({
84+
type: "text",
85+
value: line,
86+
});
87+
}
88+
}
89+
}
90+
} else if (child.type === "element") {
91+
const availableBlock = [CodeBlockSelector];
92+
const selector = availableBlock.find((selector) =>
93+
selector.filter(child),
94+
);
95+
96+
if (selector) {
97+
if (stack.length > 0) {
98+
stack[stack.length - 1].children.push(selector.render(child));
99+
} else {
100+
blocks.push(selector.render(child));
101+
}
102+
}
103+
}
104+
}
105+
106+
return blocks;
107+
};
108+
109+
return parseChildren(node.children || []);
110+
};
111+
112+
// Usage dans rehypeComponents
113+
export default function rehypeComponents(): Plugin<[], Root> {
114+
return (tree: Root) => {
115+
visit(tree, "element", (node, index, parent) => {
116+
const parsedBlocks = parseSingleNode(node);
117+
118+
parent.children[index] = {
119+
type: "element",
120+
tagName: "BlockRenderer",
121+
properties: { ast: JSON.stringify(parsedBlocks) },
122+
children: [], // ou des enfants si nécessaire
123+
};
124+
});
125+
};
126+
}

0 commit comments

Comments
 (0)