@k11k/better-blocks-astro-renderer 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/Block.astro +3 -0
- package/src/Callout.astro +17 -25
- package/src/Details.astro +84 -0
- package/src/types.ts +11 -1
package/README.md
CHANGED
|
@@ -147,6 +147,31 @@ const { blocks } = Astro.props;
|
|
|
147
147
|
<BlocksRenderer content={blocks} blocks={{ callout: MyCallout }} />
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
### Details / Summary (Collapsible)
|
|
151
|
+
|
|
152
|
+
Block-level `details` nodes render a native, keyboard-accessible `<details>` / `<summary>` disclosure with **zero client-side JavaScript** — the open/closed state is handled entirely by the browser. The `summary` field is the plain-text label, the optional `defaultOpen` boolean maps to the HTML `open` attribute (honored on initial render so screen readers get the correct state), and `children` are block-level content (paragraphs, lists, tables, images, and nested `details`) rendered after the summary. The default markup carries stable `bb-details` and `bb-details-summary` classes.
|
|
153
|
+
|
|
154
|
+
A small **scoped `<style>`** ships with the component (still zero client-side JavaScript): a GitHub-inspired card with a rotating disclosure marker. Retheme it from your own CSS via the `--bb-details-*` custom properties (`--bb-details-border`, `--bb-details-bg`, `--bb-details-summary-bg`, `--bb-details-marker`) without replacing the markup:
|
|
155
|
+
|
|
156
|
+
```css
|
|
157
|
+
.bb-details {
|
|
158
|
+
--bb-details-border: #c8c8c8;
|
|
159
|
+
--bb-details-summary-bg: #eee;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
To replace the markup entirely, override the `details` block. It receives `summary` and `defaultOpen`; the nested children arrive via `<slot />`:
|
|
164
|
+
|
|
165
|
+
```astro
|
|
166
|
+
---
|
|
167
|
+
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';
|
|
168
|
+
import MyDetails from '../components/MyDetails.astro';
|
|
169
|
+
const { blocks } = Astro.props;
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
<BlocksRenderer content={blocks} blocks={{ details: MyDetails }} />
|
|
173
|
+
```
|
|
174
|
+
|
|
150
175
|
## Supported Blocks
|
|
151
176
|
|
|
152
177
|
| Block | Default element | Source |
|
|
@@ -165,6 +190,7 @@ const { blocks } = Astro.props;
|
|
|
165
190
|
| `math` (inline/block) | `<span>` / `<div>` | Better Blocks |
|
|
166
191
|
| `diagram` (mermaid) | `<div>` (inline SVG) | Better Blocks |
|
|
167
192
|
| `callout` (admonition) | `<aside>` | Better Blocks |
|
|
193
|
+
| `details` (collapsible) | `<details>` | Better Blocks |
|
|
168
194
|
|
|
169
195
|
### Block properties
|
|
170
196
|
|
|
@@ -186,6 +212,8 @@ const { blocks } = Astro.props;
|
|
|
186
212
|
| `value` | math | LaTeX source rendered with KaTeX |
|
|
187
213
|
| `format` | diagram | `mermaid` (the only supported diagram format) |
|
|
188
214
|
| `value` | diagram | Mermaid source, pre-rendered to SVG on the server |
|
|
215
|
+
| `summary` | details | Plain-text label for the `<summary>` |
|
|
216
|
+
| `defaultOpen` | details | Open on initial render (HTML `open` attribute) |
|
|
189
217
|
|
|
190
218
|
## Supported Modifiers
|
|
191
219
|
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k11k/better-blocks-astro-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Astro renderer for Strapi v5 Blocks content with full Better Blocks plugin support — colors, tables, to-do lists, media embeds, alignment, and more. Native Astro components, zero client-side JavaScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.ts",
|
package/src/Block.astro
CHANGED
|
@@ -6,6 +6,7 @@ import Table from './Table.astro';
|
|
|
6
6
|
import Math from './Math.astro';
|
|
7
7
|
import Diagram from './Diagram.astro';
|
|
8
8
|
import Callout from './Callout.astro';
|
|
9
|
+
import Details from './Details.astro';
|
|
9
10
|
import { getBlockStyle, getPlainText } from './utils';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
@@ -160,3 +161,5 @@ const EmbedComp = blocks?.['media-embed'] as any;
|
|
|
160
161
|
{block.type === 'diagram' && <Diagram node={block} blocks={blocks} />}
|
|
161
162
|
|
|
162
163
|
{block.type === 'callout' && <Callout node={block} blocks={blocks} modifiers={modifiers} />}
|
|
164
|
+
|
|
165
|
+
{block.type === 'details' && <Details node={block} blocks={blocks} modifiers={modifiers} />}
|
package/src/Callout.astro
CHANGED
|
@@ -16,7 +16,7 @@ interface Props {
|
|
|
16
16
|
const { node, blocks, modifiers } = Astro.props;
|
|
17
17
|
|
|
18
18
|
// GitHub-style alert metadata: default label and octicon path. Accent colors
|
|
19
|
-
// live in the scoped <style> below
|
|
19
|
+
// live in the scoped <style> below (GitHub light palette, matching the React renderer).
|
|
20
20
|
const VARIANTS: Record<CalloutVariant, { label: string; icon: string }> = {
|
|
21
21
|
note: {
|
|
22
22
|
label: 'Note',
|
|
@@ -73,6 +73,8 @@ const CalloutComp = blocks?.callout as any;
|
|
|
73
73
|
<style>
|
|
74
74
|
.bb-callout {
|
|
75
75
|
border-left: 0.25rem solid var(--bb-callout-accent, #0969da);
|
|
76
|
+
/* Subtle accent-tinted background (~8% opacity) to match the editor preview. */
|
|
77
|
+
background-color: color-mix(in srgb, var(--bb-callout-accent, #0969da) 8%, transparent);
|
|
76
78
|
padding: 0.5rem 1rem;
|
|
77
79
|
margin: 1rem 0;
|
|
78
80
|
}
|
|
@@ -80,9 +82,13 @@ const CalloutComp = blocks?.callout as any;
|
|
|
80
82
|
display: flex;
|
|
81
83
|
align-items: center;
|
|
82
84
|
gap: 0.5rem;
|
|
83
|
-
|
|
85
|
+
/* GitHub spacing: tight title line with a 1rem gap before the body. */
|
|
86
|
+
line-height: 1;
|
|
87
|
+
margin: 0 0 1rem;
|
|
84
88
|
font-weight: 600;
|
|
85
|
-
color
|
|
89
|
+
/* GitHub's caution variant uses a slightly different title color than its
|
|
90
|
+
accent/border, so allow an optional per-variant title override. */
|
|
91
|
+
color: var(--bb-callout-title-color, var(--bb-callout-accent, #0969da));
|
|
86
92
|
}
|
|
87
93
|
.bb-callout-icon {
|
|
88
94
|
width: 16px;
|
|
@@ -90,14 +96,18 @@ const CalloutComp = blocks?.callout as any;
|
|
|
90
96
|
flex: none;
|
|
91
97
|
fill: currentColor;
|
|
92
98
|
}
|
|
93
|
-
|
|
99
|
+
/* Collapse the body's outer margins so the content sits flush within the
|
|
100
|
+
callout padding (top/bottom balanced). :global is required because the body
|
|
101
|
+
blocks are rendered by other components and don't carry this component's
|
|
102
|
+
scoped id, so a plain scoped selector would never match them. */
|
|
103
|
+
.bb-callout-body > :global(:first-child) {
|
|
94
104
|
margin-top: 0;
|
|
95
105
|
}
|
|
96
|
-
.bb-callout-body > :last-child {
|
|
106
|
+
.bb-callout-body > :global(:last-child) {
|
|
97
107
|
margin-bottom: 0;
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
/*
|
|
110
|
+
/* Accent colors (GitHub light palette) — matches the React renderer. */
|
|
101
111
|
.bb-callout-note {
|
|
102
112
|
--bb-callout-accent: #0969da;
|
|
103
113
|
}
|
|
@@ -112,24 +122,6 @@ const CalloutComp = blocks?.callout as any;
|
|
|
112
122
|
}
|
|
113
123
|
.bb-callout-caution {
|
|
114
124
|
--bb-callout-accent: #cf222e;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
/* Dark-mode accents adapt automatically */
|
|
118
|
-
@media (prefers-color-scheme: dark) {
|
|
119
|
-
.bb-callout-note {
|
|
120
|
-
--bb-callout-accent: #4493f8;
|
|
121
|
-
}
|
|
122
|
-
.bb-callout-tip {
|
|
123
|
-
--bb-callout-accent: #3fb950;
|
|
124
|
-
}
|
|
125
|
-
.bb-callout-important {
|
|
126
|
-
--bb-callout-accent: #ab7df8;
|
|
127
|
-
}
|
|
128
|
-
.bb-callout-warning {
|
|
129
|
-
--bb-callout-accent: #d29922;
|
|
130
|
-
}
|
|
131
|
-
.bb-callout-caution {
|
|
132
|
-
--bb-callout-accent: #f85149;
|
|
133
|
-
}
|
|
125
|
+
--bb-callout-title-color: #d1242f;
|
|
134
126
|
}
|
|
135
127
|
</style>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { DetailsNode, CustomBlocksConfig, CustomModifiersConfig } from './types';
|
|
3
|
+
import Block from './Block.astro';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
node: DetailsNode;
|
|
7
|
+
blocks?: CustomBlocksConfig;
|
|
8
|
+
modifiers?: CustomModifiersConfig;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { node, blocks, modifiers } = Astro.props;
|
|
12
|
+
|
|
13
|
+
const DetailsComp = blocks?.details as any;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
DetailsComp ? (
|
|
18
|
+
<DetailsComp summary={node.summary} defaultOpen={node.defaultOpen}>
|
|
19
|
+
{node.children.map((child) => (
|
|
20
|
+
<Block block={child} blocks={blocks} modifiers={modifiers} />
|
|
21
|
+
))}
|
|
22
|
+
</DetailsComp>
|
|
23
|
+
) : (
|
|
24
|
+
<details class="bb-details" open={node.defaultOpen}>
|
|
25
|
+
<summary class="bb-details-summary">{node.summary}</summary>
|
|
26
|
+
<div class="bb-details-body">
|
|
27
|
+
{node.children.map((child) => (
|
|
28
|
+
<Block block={child} blocks={blocks} modifiers={modifiers} />
|
|
29
|
+
))}
|
|
30
|
+
</div>
|
|
31
|
+
</details>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
/* GitHub-inspired collapsible. Zero client-side JavaScript — the open/closed
|
|
37
|
+
state is handled natively by the <details>/<summary> elements. */
|
|
38
|
+
.bb-details {
|
|
39
|
+
border: 1px solid var(--bb-details-border, #d0d7de);
|
|
40
|
+
border-radius: 6px;
|
|
41
|
+
margin: 1rem 0;
|
|
42
|
+
background: var(--bb-details-bg, #fff);
|
|
43
|
+
}
|
|
44
|
+
.bb-details-summary {
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
padding: 0.5rem 1rem;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
list-style: none;
|
|
49
|
+
border-radius: 6px;
|
|
50
|
+
background: var(--bb-details-summary-bg, #f6f8fa);
|
|
51
|
+
}
|
|
52
|
+
/* Hide the browser's native disclosure triangle and draw our own that
|
|
53
|
+
rotates when the disclosure is open. */
|
|
54
|
+
.bb-details-summary::-webkit-details-marker {
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
57
|
+
.bb-details-summary::before {
|
|
58
|
+
content: '\25B8';
|
|
59
|
+
display: inline-block;
|
|
60
|
+
margin-right: 0.5rem;
|
|
61
|
+
color: var(--bb-details-marker, #57606a);
|
|
62
|
+
transition: transform 0.15s ease;
|
|
63
|
+
}
|
|
64
|
+
.bb-details[open] > .bb-details-summary::before {
|
|
65
|
+
transform: rotate(90deg);
|
|
66
|
+
}
|
|
67
|
+
.bb-details[open] > .bb-details-summary {
|
|
68
|
+
border-bottom: 1px solid var(--bb-details-border, #d0d7de);
|
|
69
|
+
border-radius: 6px 6px 0 0;
|
|
70
|
+
}
|
|
71
|
+
/* Inset the body content. Collapse the body's outer margins so it sits flush
|
|
72
|
+
within the box (top/bottom balanced). :global is required because the body
|
|
73
|
+
blocks are rendered by other components and don't carry this component's
|
|
74
|
+
scoped id. */
|
|
75
|
+
.bb-details-body {
|
|
76
|
+
padding: 0.5rem 1rem;
|
|
77
|
+
}
|
|
78
|
+
.bb-details-body > :global(:first-child) {
|
|
79
|
+
margin-top: 0;
|
|
80
|
+
}
|
|
81
|
+
.bb-details-body > :global(:last-child) {
|
|
82
|
+
margin-bottom: 0;
|
|
83
|
+
}
|
|
84
|
+
</style>
|
package/src/types.ts
CHANGED
|
@@ -144,6 +144,13 @@ export type CalloutNode = {
|
|
|
144
144
|
children: BlockNode[];
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
+
export type DetailsNode = {
|
|
148
|
+
type: 'details';
|
|
149
|
+
summary: string;
|
|
150
|
+
defaultOpen?: boolean;
|
|
151
|
+
children: BlockNode[];
|
|
152
|
+
};
|
|
153
|
+
|
|
147
154
|
export type BlockNode =
|
|
148
155
|
| ParagraphNode
|
|
149
156
|
| HeadingNode
|
|
@@ -156,7 +163,8 @@ export type BlockNode =
|
|
|
156
163
|
| MediaEmbedNode
|
|
157
164
|
| MathNode
|
|
158
165
|
| DiagramNode
|
|
159
|
-
| CalloutNode
|
|
166
|
+
| CalloutNode
|
|
167
|
+
| DetailsNode;
|
|
160
168
|
|
|
161
169
|
export type BlocksContent = BlockNode[];
|
|
162
170
|
|
|
@@ -196,6 +204,7 @@ export type AstroComponentFactory = (...args: any[]) => any;
|
|
|
196
204
|
* - `math` — `{ formula: string; inline: boolean }`
|
|
197
205
|
* - `diagram` — `{ code: string; format: 'mermaid' }`
|
|
198
206
|
* - `callout` — `{ variant: CalloutVariant; title?: string }` (children via `<slot />`)
|
|
207
|
+
* - `details` — `{ summary: string; defaultOpen?: boolean }` (children via `<slot />`)
|
|
199
208
|
*/
|
|
200
209
|
export type CustomBlocksConfig = Partial<{
|
|
201
210
|
paragraph: AstroComponentFactory;
|
|
@@ -215,6 +224,7 @@ export type CustomBlocksConfig = Partial<{
|
|
|
215
224
|
math: AstroComponentFactory;
|
|
216
225
|
diagram: AstroComponentFactory;
|
|
217
226
|
callout: AstroComponentFactory;
|
|
227
|
+
details: AstroComponentFactory;
|
|
218
228
|
}>;
|
|
219
229
|
|
|
220
230
|
/**
|