@cogenta/theme-docs 0.3.2 → 0.5.3

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.
Files changed (148) hide show
  1. package/dist/src/index.d.ts +2 -0
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +2 -0
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/render/blocks/accordion.d.ts +8 -0
  6. package/dist/src/render/blocks/accordion.d.ts.map +1 -1
  7. package/dist/src/render/blocks/accordion.js +10 -10
  8. package/dist/src/render/blocks/accordion.js.map +1 -1
  9. package/dist/src/render/blocks/collection-list.d.ts +3 -11
  10. package/dist/src/render/blocks/collection-list.d.ts.map +1 -1
  11. package/dist/src/render/blocks/collection-list.js +69 -68
  12. package/dist/src/render/blocks/collection-list.js.map +1 -1
  13. package/dist/src/render/blocks/cta.d.ts +6 -0
  14. package/dist/src/render/blocks/cta.d.ts.map +1 -1
  15. package/dist/src/render/blocks/cta.js +9 -4
  16. package/dist/src/render/blocks/cta.js.map +1 -1
  17. package/dist/src/render/blocks/embed.d.ts.map +1 -1
  18. package/dist/src/render/blocks/embed.js +14 -13
  19. package/dist/src/render/blocks/embed.js.map +1 -1
  20. package/dist/src/render/blocks/faq.d.ts +7 -0
  21. package/dist/src/render/blocks/faq.d.ts.map +1 -1
  22. package/dist/src/render/blocks/faq.js +10 -10
  23. package/dist/src/render/blocks/faq.js.map +1 -1
  24. package/dist/src/render/blocks/feature-grid.d.ts.map +1 -1
  25. package/dist/src/render/blocks/feature-grid.js +19 -19
  26. package/dist/src/render/blocks/feature-grid.js.map +1 -1
  27. package/dist/src/render/blocks/gallery.d.ts +8 -4
  28. package/dist/src/render/blocks/gallery.d.ts.map +1 -1
  29. package/dist/src/render/blocks/gallery.js +19 -13
  30. package/dist/src/render/blocks/gallery.js.map +1 -1
  31. package/dist/src/render/blocks/hero.d.ts +12 -9
  32. package/dist/src/render/blocks/hero.d.ts.map +1 -1
  33. package/dist/src/render/blocks/hero.js +22 -20
  34. package/dist/src/render/blocks/hero.js.map +1 -1
  35. package/dist/src/render/blocks/logo-strip.d.ts +6 -0
  36. package/dist/src/render/blocks/logo-strip.d.ts.map +1 -1
  37. package/dist/src/render/blocks/logo-strip.js +6 -10
  38. package/dist/src/render/blocks/logo-strip.js.map +1 -1
  39. package/dist/src/render/blocks/logos.d.ts +10 -0
  40. package/dist/src/render/blocks/logos.d.ts.map +1 -1
  41. package/dist/src/render/blocks/logos.js +20 -17
  42. package/dist/src/render/blocks/logos.js.map +1 -1
  43. package/dist/src/render/blocks/media-figure.d.ts +0 -6
  44. package/dist/src/render/blocks/media-figure.d.ts.map +1 -1
  45. package/dist/src/render/blocks/media-figure.js +28 -16
  46. package/dist/src/render/blocks/media-figure.js.map +1 -1
  47. package/dist/src/render/blocks/pricing-table.d.ts.map +1 -1
  48. package/dist/src/render/blocks/pricing-table.js +16 -20
  49. package/dist/src/render/blocks/pricing-table.js.map +1 -1
  50. package/dist/src/render/blocks/prose.d.ts +13 -1
  51. package/dist/src/render/blocks/prose.d.ts.map +1 -1
  52. package/dist/src/render/blocks/prose.js +12 -31
  53. package/dist/src/render/blocks/prose.js.map +1 -1
  54. package/dist/src/render/blocks/quote.d.ts +5 -3
  55. package/dist/src/render/blocks/quote.d.ts.map +1 -1
  56. package/dist/src/render/blocks/quote.js +10 -14
  57. package/dist/src/render/blocks/quote.js.map +1 -1
  58. package/dist/src/render/blocks/stat-counter.d.ts +5 -0
  59. package/dist/src/render/blocks/stat-counter.d.ts.map +1 -1
  60. package/dist/src/render/blocks/stat-counter.js +6 -10
  61. package/dist/src/render/blocks/stat-counter.js.map +1 -1
  62. package/dist/src/render/blocks/stats.d.ts +10 -0
  63. package/dist/src/render/blocks/stats.d.ts.map +1 -1
  64. package/dist/src/render/blocks/stats.js +13 -8
  65. package/dist/src/render/blocks/stats.js.map +1 -1
  66. package/dist/src/render/blocks/testimonial.d.ts +4 -5
  67. package/dist/src/render/blocks/testimonial.d.ts.map +1 -1
  68. package/dist/src/render/blocks/testimonial.js +9 -13
  69. package/dist/src/render/blocks/testimonial.js.map +1 -1
  70. package/dist/src/render/chrome.d.ts +36 -1
  71. package/dist/src/render/chrome.d.ts.map +1 -1
  72. package/dist/src/render/chrome.js +122 -80
  73. package/dist/src/render/chrome.js.map +1 -1
  74. package/dist/src/render/doc-index.d.ts +25 -0
  75. package/dist/src/render/doc-index.d.ts.map +1 -0
  76. package/dist/src/render/doc-index.js +49 -0
  77. package/dist/src/render/doc-index.js.map +1 -0
  78. package/dist/src/render/doc-page.d.ts +6 -0
  79. package/dist/src/render/doc-page.d.ts.map +1 -0
  80. package/dist/src/render/doc-page.js +127 -0
  81. package/dist/src/render/doc-page.js.map +1 -0
  82. package/dist/src/render/layout.d.ts +23 -0
  83. package/dist/src/render/layout.d.ts.map +1 -0
  84. package/dist/src/render/layout.js +40 -0
  85. package/dist/src/render/layout.js.map +1 -0
  86. package/dist/src/render/render-block.d.ts +9 -6
  87. package/dist/src/render/render-block.d.ts.map +1 -1
  88. package/dist/src/render/render-block.js +47 -100
  89. package/dist/src/render/render-block.js.map +1 -1
  90. package/dist/src/render/rich-text.d.ts +26 -0
  91. package/dist/src/render/rich-text.d.ts.map +1 -0
  92. package/dist/src/render/rich-text.js +325 -0
  93. package/dist/src/render/rich-text.js.map +1 -0
  94. package/dist/src/render/search.d.ts +15 -0
  95. package/dist/src/render/search.d.ts.map +1 -0
  96. package/dist/src/render/search.js +31 -0
  97. package/dist/src/render/search.js.map +1 -0
  98. package/dist/src/render/strings.d.ts +51 -0
  99. package/dist/src/render/strings.d.ts.map +1 -0
  100. package/dist/src/render/strings.js +60 -0
  101. package/dist/src/render/strings.js.map +1 -0
  102. package/dist/src/render/term-archive.d.ts +5 -5
  103. package/dist/src/render/term-archive.d.ts.map +1 -1
  104. package/dist/src/render/term-archive.js +27 -16
  105. package/dist/src/render/term-archive.js.map +1 -1
  106. package/dist/src/render/widget-areas.d.ts +22 -0
  107. package/dist/src/render/widget-areas.d.ts.map +1 -0
  108. package/dist/src/render/widget-areas.js +29 -0
  109. package/dist/src/render/widget-areas.js.map +1 -0
  110. package/package.json +4 -4
  111. package/src/index.ts +2 -0
  112. package/src/render/blocks/accordion.ts +31 -42
  113. package/src/render/blocks/collection-list.ts +132 -123
  114. package/src/render/blocks/cta.ts +23 -18
  115. package/src/render/blocks/embed.ts +37 -28
  116. package/src/render/blocks/faq.ts +26 -37
  117. package/src/render/blocks/feature-grid.ts +34 -42
  118. package/src/render/blocks/gallery.ts +33 -29
  119. package/src/render/blocks/hero.ts +41 -45
  120. package/src/render/blocks/logo-strip.ts +21 -21
  121. package/src/render/blocks/logos.ts +32 -42
  122. package/src/render/blocks/media-figure.ts +47 -25
  123. package/src/render/blocks/pricing-table.ts +34 -41
  124. package/src/render/blocks/prose.ts +25 -40
  125. package/src/render/blocks/quote.ts +25 -19
  126. package/src/render/blocks/stat-counter.ts +27 -29
  127. package/src/render/blocks/stats.ts +35 -32
  128. package/src/render/blocks/testimonial.ts +18 -19
  129. package/src/render/chrome.ts +170 -83
  130. package/src/render/doc-index.ts +65 -0
  131. package/src/render/doc-page.ts +303 -0
  132. package/src/render/layout.ts +73 -0
  133. package/src/render/render-block.ts +67 -191
  134. package/src/render/rich-text.ts +446 -0
  135. package/src/render/search.ts +41 -0
  136. package/src/render/strings.ts +67 -0
  137. package/src/render/term-archive.ts +62 -38
  138. package/src/render/widget-areas.ts +33 -0
  139. package/src/styles/archive.css +21 -27
  140. package/src/styles/base.css +438 -838
  141. package/src/styles/blocks.css +658 -916
  142. package/src/styles/chrome.css +439 -0
  143. package/src/styles/docs.css +404 -0
  144. package/src/styles/rich.css +337 -0
  145. package/src/styles/theme.css +28 -23
  146. package/src/styles/tokens.css +209 -215
  147. package/src/styles/widgets.css +890 -0
  148. package/tokens.json +15 -15
@@ -1,72 +1,65 @@
1
1
  import type { PricingTableBlock, PricingTier } from '@cogenta/blocks'
2
2
  import {
3
3
  actionLink,
4
- blockHeadingTag,
5
- type HeadingTag,
6
4
  type HtmlElement,
7
5
  h,
8
- heading,
9
6
  nestedHeadingTag,
10
7
  type RenderContext,
11
8
  } from '@cogenta/theme-kit'
9
+ import { optionalText, section, sectionHead } from '../layout.js'
10
+ import { word } from '../strings.js'
12
11
 
13
12
  /**
14
- * `blocks@2.0` (RFC 0001). Bordered plates, one lifted (border + a small
15
- * "recommended" rule) when `highlighted` is set — an editorial signal,
16
- * never a colour: it becomes `data-highlighted` for the stylesheet, and
17
- * `aria-current` so the emphasis is announced, not only shown.
13
+ * Plans, the way a documentation site shows them (support plans, usage
14
+ * limits): columns side by side between hairlines, never cards. Each plan
15
+ * names itself, prints its price in tabular numerals with the interval after
16
+ * it, lists what it includes as ruled lines, and ends on its action. The
17
+ * recommended plan is marked by a rule in ink above it and a word, never by a
18
+ * tinted background.
18
19
  */
19
- function renderTier(tier: PricingTier, ctx: RenderContext, tag: HeadingTag): HtmlElement {
20
+ function tier(item: PricingTier, ctx: RenderContext, tag: string): HtmlElement {
21
+ const highlighted = item.highlighted === true
20
22
  return h(
21
23
  'li',
22
- {
23
- class: 'cg-pricing__tier',
24
- 'data-highlighted': tier.highlighted === true ? 'true' : undefined,
25
- 'aria-current': tier.highlighted === true ? 'true' : undefined,
26
- },
27
- heading(tag, { class: 'cg-pricing__name' }, tier.name),
24
+ { class: 'cd-plans__item', 'data-highlighted': highlighted ? 'true' : 'false' },
25
+ h(
26
+ 'div',
27
+ { class: 'cd-plans__head' },
28
+ h(tag, { class: 'cd-plans__name' }, item.name),
29
+ highlighted ? h('p', { class: 'cd-plans__flag' }, word(ctx.locale, 'recommended')) : null,
30
+ ),
28
31
  h(
29
32
  'p',
30
- { class: 'cg-pricing__price' },
31
- h('span', { class: 'cg-pricing__amount' }, tier.price),
32
- tier.interval === undefined
33
- ? null
34
- : h('span', { class: 'cg-pricing__interval' }, tier.interval),
33
+ { class: 'cd-plans__price' },
34
+ h('span', { class: 'cd-plans__amount' }, item.price),
35
+ optionalText('span', 'cd-plans__interval', item.interval),
35
36
  ),
36
- tier.features.length === 0
37
+ item.features.length === 0
37
38
  ? null
38
39
  : h(
39
40
  'ul',
40
- { class: 'cg-pricing__features' },
41
- tier.features.map((feature) => h('li', { class: 'cg-pricing__feature' }, feature)),
41
+ { class: 'cd-plans__features' },
42
+ item.features.map((feature) => h('li', { class: 'cd-plans__feature' }, feature)),
42
43
  ),
43
- tier.action === undefined
44
+ item.action === undefined
44
45
  ? null
45
- : h(
46
- 'div',
47
- { class: 'cg-pricing__action' },
48
- actionLink(ctx, { ...tier.action, emphasis: tier.action.emphasis ?? 'primary' }),
49
- ),
46
+ : h('div', { class: 'cd-plans__action' }, actionLink(ctx, item.action)),
50
47
  )
51
48
  }
52
49
 
53
50
  export function renderPricingTable(block: PricingTableBlock, ctx: RenderContext): HtmlElement {
54
- const hasTitle = block.title !== undefined
55
- const tierTag = nestedHeadingTag('pricingTable', hasTitle)
56
- return h(
51
+ const tag = nestedHeadingTag('pricingTable', block.title !== undefined)
52
+ return section(
57
53
  'section',
58
- { class: 'cg-block cg-pricing', 'data-block': 'pricingTable' },
59
- hasTitle
60
- ? heading(
61
- blockHeadingTag('pricingTable') ?? 'h2',
62
- { class: 'cg-pricing__title', 'data-field': 'title' },
63
- block.title ?? '',
64
- )
65
- : null,
54
+ 'pricingTable',
55
+ 'cd-plans',
56
+ { 'data-count': String(Math.min(block.tiers.length, 4)) },
57
+ 'div',
58
+ sectionHead('pricingTable', block.title),
66
59
  h(
67
60
  'ul',
68
- { class: 'cg-pricing__tiers' },
69
- block.tiers.map((tier) => renderTier(tier, ctx, tierTag)),
61
+ { class: 'cd-plans__items' },
62
+ block.tiers.map((item) => tier(item, ctx, tag)),
70
63
  ),
71
64
  )
72
65
  }
@@ -1,49 +1,34 @@
1
1
  import type { ProseBlock } from '@cogenta/blocks'
2
- import {
3
- type HtmlElement,
4
- type HtmlNode,
5
- h,
6
- type RenderContext,
7
- renderRichText,
8
- } from '@cogenta/theme-kit'
2
+ import { type HtmlElement, h, type RenderContext } from '@cogenta/theme-kit'
3
+ import { type HeadingAnchors, headingAnchors, renderDocsRichText } from '../rich-text.js'
9
4
 
10
5
  /**
11
- * `prose` declares `headingLevel: 'none'`: it contributes no heading of its
12
- * own. Whatever headings appear come from the rich text document, whose
13
- * vocabulary starts at `h2`.
6
+ * Running text. `prose` declares `headingLevel: 'none'`: its headings come
7
+ * from the rich text document, whose vocabulary starts at `h2`.
14
8
  *
15
- * Contract A's rich text schema (`@cogenta/schema`, frozen) has no fenced
16
- * "code block" node — the only code-shaped thing it can carry is a
17
- * paragraph whose sole span carries the `code` mark, which
18
- * `renderRichText` (`@cogenta/theme-kit`) already turns into
19
- * `<p><code>…</code></p>`. A documentation theme without a real, multi-line
20
- * code block would be dishonest about what it is for, so this is the one
21
- * place in the theme that post-processes theme-kit's own output rather than
22
- * writing a second rich-text renderer: a `<p>` whose only child is a bare
23
- * `<code>` is promoted to `<pre><code>`, everything else is untouched. The
24
- * transform only ever changes two tag names on an already-escaped tree — no
25
- * new content is invented, and R3 (a block never stores HTML) still holds,
26
- * since the source is still the same structured span the editor wrote.
9
+ * The body is set on the reading measure from the first column; code blocks,
10
+ * notes and reference tables are read out of the document by
11
+ * `renderDocsRichText` (see its own comment for the four shapes).
12
+ *
13
+ * `anchors` is the page's own set of heading ids, so the table of contents
14
+ * and the headings agree; a prose block rendered on its own computes its own.
27
15
  */
28
- function promoteCodeBlocks(nodes: readonly HtmlNode[]): readonly HtmlNode[] {
29
- return nodes.map((node) => {
30
- if (node.kind !== 'element') return node
31
- if (
32
- node.tag === 'p' &&
33
- node.children.length === 1 &&
34
- node.children[0]?.kind === 'element' &&
35
- node.children[0].tag === 'code'
36
- ) {
37
- return h('pre', { class: 'cg-prose__code' }, node.children[0])
38
- }
39
- return node
40
- })
41
- }
42
-
43
- export function renderProse(block: ProseBlock, ctx: RenderContext): HtmlElement {
16
+ export function renderProse(
17
+ block: ProseBlock,
18
+ ctx: RenderContext,
19
+ anchors: HeadingAnchors = headingAnchors([block]),
20
+ ): HtmlElement {
44
21
  return h(
45
22
  'div',
46
- { class: 'cg-block cg-prose', 'data-block': 'prose' },
47
- promoteCodeBlocks(renderRichText(ctx, block.body)),
23
+ { class: 'cd-section cd-prose', 'data-block': 'prose' },
24
+ h(
25
+ 'div',
26
+ { class: 'cd-container cd-prose__inner' },
27
+ h(
28
+ 'div',
29
+ { class: 'cd-prose__body cd-rich' },
30
+ renderDocsRichText(ctx, block.body, { anchors: { blockKey: block._key, map: anchors } }),
31
+ ),
32
+ ),
48
33
  )
49
34
  }
@@ -1,34 +1,40 @@
1
1
  import type { QuoteBlock } from '@cogenta/blocks'
2
2
  import { type HtmlElement, h, image, type RenderContext } from '@cogenta/theme-kit'
3
+ import { optionalText, section } from '../layout.js'
3
4
 
4
5
  /**
5
- * `<figure><blockquote>…</blockquote><figcaption>` is the attribution
6
- * pattern the HTML spec prescribes: putting the author inside the
7
- * `<blockquote>` would claim the author's name is part of what was said.
6
+ * A quotation: in documentation, a line from an RFC, a maintainer or a team
7
+ * that runs the product. Set at the quote size on the reading measure, in
8
+ * real curly quotation marks drawn by the stylesheet, hanging from a rule in
9
+ * ink on its left; the attribution under it, with a small round portrait when
10
+ * there is one.
8
11
  */
9
12
  export function renderQuote(block: QuoteBlock, ctx: RenderContext): HtmlElement {
10
- const hasAttribution =
11
- block.author !== undefined || block.role !== undefined || block.avatar !== undefined
12
- return h(
13
+ const hasAttribution = block.author !== undefined || block.role !== undefined
14
+ return section(
15
+ 'div',
16
+ 'quote',
17
+ 'cd-quote',
18
+ {},
13
19
  'figure',
14
- { class: 'cg-block cg-quote', 'data-block': 'quote' },
15
- h('blockquote', { class: 'cg-quote__text' }, h('p', { 'data-field': 'text' }, block.text)),
20
+ h(
21
+ 'blockquote',
22
+ { class: 'cd-quote__body' },
23
+ h('p', { class: 'cd-quote__text', 'data-field': 'text' }, block.text),
24
+ ),
16
25
  hasAttribution
17
26
  ? h(
18
27
  'figcaption',
19
- { class: 'cg-quote__attribution' },
28
+ { class: 'cd-person' },
20
29
  block.avatar === undefined
21
30
  ? null
22
- : image(ctx, block.avatar, {
23
- className: 'cg-quote__avatar',
24
- variant: { width: 96, height: 96, fit: 'cover' },
25
- }),
26
- block.author === undefined
27
- ? null
28
- : h('span', { class: 'cg-quote__author', 'data-field': 'author' }, block.author),
29
- block.role === undefined
30
- ? null
31
- : h('span', { class: 'cg-quote__role', 'data-field': 'role' }, block.role),
31
+ : image(ctx, block.avatar, { className: 'cd-person__avatar', sizes: '2.5rem' }),
32
+ h(
33
+ 'span',
34
+ { class: 'cd-person__words' },
35
+ optionalText('span', 'cd-person__name', block.author, { 'data-field': 'author' }),
36
+ optionalText('span', 'cd-person__role', block.role, { 'data-field': 'role' }),
37
+ ),
32
38
  )
33
39
  : null,
34
40
  )
@@ -1,37 +1,35 @@
1
- import type { StatCounterBlock, StatCounterItem } from '@cogenta/blocks'
2
- import {
3
- blockHeadingTag,
4
- type HtmlElement,
5
- h,
6
- heading,
7
- type RenderContext,
8
- } from '@cogenta/theme-kit'
1
+ import type { StatCounterBlock } from '@cogenta/blocks'
2
+ import { type HtmlElement, h, type RenderContext } from '@cogenta/theme-kit'
3
+ import { section, sectionHead } from '../layout.js'
9
4
 
10
5
  /**
11
- * `blocks@2.0` (RFC 0001). A `<dl>`, exactly like `stats`: the label sits
12
- * before the figure in markup (WCAG 1.3.2); the stylesheet paints the big
13
- * figure first.
6
+ * A row of counts ("Releases this year", "Supported runtimes"): the same
7
+ * ruled, tabular register as `stats`, without units. Nothing counts up on
8
+ * scroll; the figure is simply printed.
14
9
  */
15
- function renderItem(item: StatCounterItem): HtmlElement {
16
- return h(
17
- 'div',
18
- { class: 'cg-kpi' },
19
- h('dt', { class: 'cg-kpi__label' }, item.label),
20
- h('dd', { class: 'cg-kpi__value' }, item.value),
21
- )
22
- }
23
-
24
10
  export function renderStatCounter(block: StatCounterBlock, _ctx: RenderContext): HtmlElement {
25
- return h(
11
+ return section(
26
12
  'section',
27
- { class: 'cg-block cg-kpis', 'data-block': 'statCounter' },
28
- block.title === undefined
29
- ? null
30
- : heading(
31
- blockHeadingTag('statCounter') ?? 'h2',
32
- { class: 'cg-kpis__title', 'data-field': 'title' },
33
- block.title,
13
+ 'statCounter',
14
+ 'cd-figures',
15
+ { 'data-count': String(Math.min(block.stats.length, 4)) },
16
+ 'div',
17
+ sectionHead('statCounter', block.title),
18
+ h(
19
+ 'dl',
20
+ { class: 'cd-figures__items' },
21
+ block.stats.map((stat) =>
22
+ h(
23
+ 'div',
24
+ { class: 'cd-figures__item' },
25
+ h('dt', { class: 'cd-figures__label' }, stat.label),
26
+ h(
27
+ 'dd',
28
+ { class: 'cd-figures__value' },
29
+ h('span', { class: 'cd-figures__number' }, stat.value),
30
+ ),
34
31
  ),
35
- h('dl', { class: 'cg-kpis__items' }, block.stats.map(renderItem)),
32
+ ),
33
+ ),
36
34
  )
37
35
  }
@@ -1,38 +1,41 @@
1
- import type { StatItem, StatsBlock } from '@cogenta/blocks'
2
- import {
3
- blockHeadingTag,
4
- type HtmlElement,
5
- h,
6
- heading,
7
- type RenderContext,
8
- } from '@cogenta/theme-kit'
9
-
10
- /** A description list: each figure is the description of its label. */
11
- function renderItem(item: StatItem): HtmlElement {
12
- return h(
13
- 'div',
14
- { class: 'cg-stat' },
15
- h('dt', { class: 'cg-stat__label' }, item.label),
16
- h(
17
- 'dd',
18
- { class: 'cg-stat__value' },
19
- item.value,
20
- item.unit === undefined ? null : h('span', { class: 'cg-stat__unit' }, item.unit),
21
- ),
22
- )
23
- }
1
+ import type { StatsBlock } from '@cogenta/blocks'
2
+ import { type HtmlElement, h, type RenderContext } from '@cogenta/theme-kit'
3
+ import { optionalText, section, sectionHead } from '../layout.js'
24
4
 
5
+ /**
6
+ * Figures the way a benchmark or a limits page prints them: a ruled row of
7
+ * up to four columns, each value in tabular numerals with its unit smaller on
8
+ * the same baseline, and a one-line label under it. The rule above each
9
+ * figure is the only structure; there is no box behind the row.
10
+ *
11
+ * A `<dl>`, since each figure is the value of its label; the label comes
12
+ * first in the markup (so it is announced first) and is drawn under the
13
+ * figure.
14
+ */
25
15
  export function renderStats(block: StatsBlock, _ctx: RenderContext): HtmlElement {
26
- return h(
16
+ return section(
27
17
  'section',
28
- { class: 'cg-block cg-stats', 'data-block': 'stats' },
29
- block.title === undefined
30
- ? null
31
- : heading(
32
- blockHeadingTag('stats') ?? 'h2',
33
- { class: 'cg-stats__title', 'data-field': 'title' },
34
- block.title,
18
+ 'stats',
19
+ 'cd-figures',
20
+ { 'data-count': String(Math.min(block.items.length, 4)) },
21
+ 'div',
22
+ sectionHead('stats', block.title),
23
+ h(
24
+ 'dl',
25
+ { class: 'cd-figures__items' },
26
+ block.items.map((item) =>
27
+ h(
28
+ 'div',
29
+ { class: 'cd-figures__item' },
30
+ h('dt', { class: 'cd-figures__label' }, item.label),
31
+ h(
32
+ 'dd',
33
+ { class: 'cd-figures__value' },
34
+ h('span', { class: 'cd-figures__number' }, item.value),
35
+ optionalText('span', 'cd-figures__unit', item.unit),
36
+ ),
35
37
  ),
36
- h('dl', { class: 'cg-stats__items' }, block.items.map(renderItem)),
38
+ ),
39
+ ),
37
40
  )
38
41
  }
@@ -1,35 +1,34 @@
1
1
  import type { TestimonialBlock } from '@cogenta/blocks'
2
- import { type HtmlElement, h, image, type RenderContext, renderRichText } from '@cogenta/theme-kit'
2
+ import { type HtmlElement, h, image, type RenderContext } from '@cogenta/theme-kit'
3
+ import { optionalText, section } from '../layout.js'
4
+ import { renderDocsRichText } from '../rich-text.js'
3
5
 
4
6
  /**
5
- * `blocks@2.0` (RFC 0001). The same `<figure><blockquote>…</blockquote>
6
- * <figcaption>` pattern `quote` uses, kept consistent so the two read as
7
- * siblings — the quote itself is rich text here, and the attribution is
8
- * the block's single grouped `attribution` field rather than three loose
9
- * ones.
7
+ * One person's account of using the product: the same register as `quote`
8
+ * (a rule in ink on the left, real quotation marks), with rich text, so a
9
+ * testimonial can hold a second paragraph or a command in `code`. The first
10
+ * paragraph is set at the quote size, the rest at the reading size.
10
11
  */
11
12
  export function renderTestimonial(block: TestimonialBlock, ctx: RenderContext): HtmlElement {
12
13
  const { attribution } = block
13
- return h(
14
+ return section(
15
+ 'div',
16
+ 'testimonial',
17
+ 'cd-testimonial',
18
+ {},
14
19
  'figure',
15
- { class: 'cg-block cg-testimonial', 'data-block': 'testimonial' },
16
- h('blockquote', { class: 'cg-testimonial__quote' }, renderRichText(ctx, block.quote)),
20
+ h('blockquote', { class: 'cd-testimonial__quote' }, renderDocsRichText(ctx, block.quote)),
17
21
  h(
18
22
  'figcaption',
19
- { class: 'cg-testimonial__attribution' },
23
+ { class: 'cd-person' },
20
24
  attribution.avatar === undefined
21
25
  ? null
22
- : image(ctx, attribution.avatar, {
23
- className: 'cg-testimonial__avatar',
24
- variant: { width: 96, height: 96, fit: 'cover' },
25
- }),
26
+ : image(ctx, attribution.avatar, { className: 'cd-person__avatar', sizes: '2.5rem' }),
26
27
  h(
27
28
  'span',
28
- { class: 'cg-testimonial__who' },
29
- h('span', { class: 'cg-testimonial__name' }, attribution.name),
30
- attribution.role === undefined
31
- ? null
32
- : h('span', { class: 'cg-testimonial__role' }, attribution.role),
29
+ { class: 'cd-person__words' },
30
+ h('span', { class: 'cd-person__name' }, attribution.name),
31
+ optionalText('span', 'cd-person__role', attribution.role),
33
32
  ),
34
33
  ),
35
34
  )