@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 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** &mdash; 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
@@ -24,6 +24,7 @@ export type {
24
24
  DiagramNode,
25
25
  CalloutNode,
26
26
  CalloutVariant,
27
+ DetailsNode,
27
28
  TextAlign,
28
29
  StyleValue,
29
30
  CustomBlocksConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k11k/better-blocks-astro-renderer",
3
- "version": "0.4.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 so they can adapt to dark mode.
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
- margin: 0 0 0.25rem;
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: var(--bb-callout-accent, #0969da);
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
- .bb-callout-body > :first-child {
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
- /* Light-mode accents (GitHub palette) */
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
  /**