Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import rehypeMermaid from "rehype-mermaid";
import rehypeCodeGroupReact from "./src/lib/plugins/code-group/plugin";
import rehypeReadMoreReact from "./src/lib/plugins/read-more/plugin";
import rehypeBlogListReact from "./src/lib/plugins/blog-list/plugin";
import rehypeBlock from "./src/lib/plugins/parser/plugin";
import {
default as remarkDirective,
default as remarkReadMoreDirective,
Expand Down Expand Up @@ -51,6 +52,7 @@ export default defineConfig({
},
remarkPlugins: [remarkDirective, remarkReadMoreDirective],
rehypePlugins: [
rehypeBlock,
rehypeMermaid,
[
rehypeCallouts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ collection:
- code-block
- markdown
- text
- card
---
128 changes: 128 additions & 0 deletions content/docs/documentation/foundamentals/components/card.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
title: Card
description: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
permalink: card
icon: lucide:bell
---

# Cards

This component allows you to group text elements while providing visual impact.

```
:::card-group
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::
```

:::card-group {cols=2}
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::

---

## Configuration

### Card per line

You can customize the number of elements per line by setting the `cols` prop.

> [!note]
> Default card per line was fixed to 2

```
:::card-group {cols=4}
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::
```

:::card-group {cols=4}
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::

If the elements cannot be aligned horizontally, they will be moved to the next line.

```
:::card-group {cols=2}
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::
```

:::card-group {cols=2}
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::

### Card item

Vous pouvez personaliser chacune de vos cartes en utilisant les props suivantes :

- `label`: Le texte à afficher en haut de la carte. (required)
- `icon`: L'icône à afficher en haut de la carte.

> [!note]
> The cards use the [`iconify`](https://icon-sets.iconify.design/) library to display icons.

```
:::card-group
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:user"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::
```

:::card-group {cols=2}
:::card {label="Test", icon="lucide:bell"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::card {label="Test", icon="lucide:user"}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::
:::
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ icon: lucide:info
## Introduction

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.

:::card-group{cols=2}
:::
25 changes: 25 additions & 0 deletions src/lib/components/content/card-group/card-group.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
type Props = {
cols?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
};

const { cols } = Astro.props;
const gridCols = {
1: "grid-cols-1",
2: "grid-cols-2",
3: "grid-cols-3",
4: "grid-cols-4",
5: "grid-cols-5",
6: "grid-cols-6",
7: "grid-cols-7",
8: "grid-cols-8",
9: "grid-cols-9",
10: "grid-cols-10",
11: "grid-cols-11",
12: "grid-cols-12",
};
---

<div class:list={["grid gap-5 pt-5 pb-10", gridCols[cols ?? 2]]}>
<slot />
</div>
30 changes: 30 additions & 0 deletions src/lib/components/content/card-group/card.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
import { Icon } from "@iconify/react";

type props = {
label: string;
icon?: string;
};

const props = Astro.props;
---

<div
class="flex flex-col p-6 border rounded-xl group hover:bg-secondary w-full"
>
<div
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"
>
<Icon
client:load
icon={props.icon}
className="group-hover:text-primary duration-150"
/>
</div>
<div class="mt-4">
<p class="font-semibold text-gray-900 !mt-0 !p-0">{props.label}</p>
<p class="text-sm text-gray-500 !mt-0 !p-0">
<slot />
</p>
</div>
</div>
10 changes: 10 additions & 0 deletions src/lib/components/content/codeblock.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
const props = Astro.props;
---

<p>
<pre
class="astro-code astro-code-themes github-light catppuccin-frappe has-highlighted"
set:html={props.html}
/>
</p>
126 changes: 126 additions & 0 deletions src/lib/plugins/card-group/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import type { Root } from "unist";
import { visit } from "unist-util-visit";
import { CodeBlockSelector } from "./selector";

// Exemple de mapping startTag -> composant Astro
const mdx: Record<string, any> = {
"card-group": "CardGroup",
card: "Card",
codeblock: "Codeblock",
};

interface Block {
delimiter: string;
startTag: string;
attributes: Record<string, string>;
children: Array<Block | { type: "text"; value: string }>;
}

const parseAttributes = (str: string): Record<string, any> => {
const regex = /(\w+)\s*=\s*(?:"([^"]*)"|(\S+))/g;
const attrs: Record<string, any> = {};
let match;

while ((match = regex.exec(str)) !== null) {
const key = match[1];
let value: any;

// Valeur entre guillemets
if (match[2] !== undefined) {
const raw = match[2];

// Essayer de parser JSON (array, object, number, boolean)
try {
value = JSON.parse(raw);
} catch {
value = raw; // fallback string
}
} else if (match[3] !== undefined) {
// Valeur non-quoted (true, false, number, etc.)
const raw = match[3];
if (raw === "true") value = true;
else if (raw === "false") value = false;
else if (!isNaN(Number(raw))) value = Number(raw);
else value = raw;
}

attrs[key] = value;
}

return attrs;
};

const parseSingleNode = (node: { type: string; children?: any[] }): Block[] => {
const parseChildren = (children: any[]): Block[] => {
const blocks: Block[] = [];
const stack: Block[] = [];

for (const child of children) {
if (child.type === "text" || child.type === "mdxTextExpression") {
const lines = child.value.split(/\r?\n/);
for (const line of lines) {
const startMatch = line.match(/^:::(\w[\w-]*)\s*(.*)$/);
const endMatch = line.match(/^:::/);

if (startMatch) {
const block: Block = {
delimiter: ":::",
startTag: startMatch[1],
attributes: parseAttributes(startMatch[2] || ""),
children: [],
};
stack.push(block);
} else if (endMatch) {
const finished = stack.pop();
if (!finished) continue;
if (stack.length > 0) {
stack[stack.length - 1].children.push(finished);
} else {
blocks.push(finished);
}
} else {
if (stack.length > 0) {
stack[stack.length - 1].children.push({
type: "text",
value: line,
});
}
}
}
} else if (child.type === "element") {
const availableBlock = [CodeBlockSelector];
const selector = availableBlock.find((selector) =>
selector.filter(child),
);

if (selector) {
if (stack.length > 0) {
stack[stack.length - 1].children.push(selector.render(child));
} else {
blocks.push(selector.render(child));
}
}
}
}

return blocks;
};

return parseChildren(node.children || []);
};

// Usage dans rehypeComponents
export default function rehypeComponents(): Plugin<[], Root> {
return (tree: Root) => {
visit(tree, "element", (node, index, parent) => {
const parsedBlocks = parseSingleNode(node);

parent.children[index] = {
type: "element",
tagName: "BlockRenderer",
properties: { ast: JSON.stringify(parsedBlocks) },
children: [], // ou des enfants si nécessaire
};
});
};
}
Loading
Loading