@cogenta/theme-magazine 1.1.2
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/LICENSE +373 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/render/blocks/accordion.d.ts +4 -0
- package/dist/src/render/blocks/accordion.d.ts.map +1 -0
- package/dist/src/render/blocks/accordion.js +20 -0
- package/dist/src/render/blocks/accordion.js.map +1 -0
- package/dist/src/render/blocks/collection-list.d.ts +5 -0
- package/dist/src/render/blocks/collection-list.d.ts.map +1 -0
- package/dist/src/render/blocks/collection-list.js +134 -0
- package/dist/src/render/blocks/collection-list.js.map +1 -0
- package/dist/src/render/blocks/cta.d.ts +10 -0
- package/dist/src/render/blocks/cta.d.ts.map +1 -0
- package/dist/src/render/blocks/cta.js +16 -0
- package/dist/src/render/blocks/cta.js.map +1 -0
- package/dist/src/render/blocks/embed.d.ts +4 -0
- package/dist/src/render/blocks/embed.d.ts.map +1 -0
- package/dist/src/render/blocks/embed.js +76 -0
- package/dist/src/render/blocks/embed.js.map +1 -0
- package/dist/src/render/blocks/faq.d.ts +4 -0
- package/dist/src/render/blocks/faq.d.ts.map +1 -0
- package/dist/src/render/blocks/faq.js +18 -0
- package/dist/src/render/blocks/faq.js.map +1 -0
- package/dist/src/render/blocks/feature-grid.d.ts +4 -0
- package/dist/src/render/blocks/feature-grid.d.ts.map +1 -0
- package/dist/src/render/blocks/feature-grid.js +27 -0
- package/dist/src/render/blocks/feature-grid.js.map +1 -0
- package/dist/src/render/blocks/gallery.d.ts +13 -0
- package/dist/src/render/blocks/gallery.d.ts.map +1 -0
- package/dist/src/render/blocks/gallery.js +26 -0
- package/dist/src/render/blocks/gallery.js.map +1 -0
- package/dist/src/render/blocks/hero.d.ts +11 -0
- package/dist/src/render/blocks/hero.d.ts.map +1 -0
- package/dist/src/render/blocks/hero.js +26 -0
- package/dist/src/render/blocks/hero.js.map +1 -0
- package/dist/src/render/blocks/logo-strip.d.ts +4 -0
- package/dist/src/render/blocks/logo-strip.d.ts.map +1 -0
- package/dist/src/render/blocks/logo-strip.js +20 -0
- package/dist/src/render/blocks/logo-strip.js.map +1 -0
- package/dist/src/render/blocks/logos.d.ts +4 -0
- package/dist/src/render/blocks/logos.d.ts.map +1 -0
- package/dist/src/render/blocks/logos.js +27 -0
- package/dist/src/render/blocks/logos.js.map +1 -0
- package/dist/src/render/blocks/media-figure.d.ts +15 -0
- package/dist/src/render/blocks/media-figure.d.ts.map +1 -0
- package/dist/src/render/blocks/media-figure.js +32 -0
- package/dist/src/render/blocks/media-figure.js.map +1 -0
- package/dist/src/render/blocks/pricing-table.d.ts +4 -0
- package/dist/src/render/blocks/pricing-table.d.ts.map +1 -0
- package/dist/src/render/blocks/pricing-table.js +32 -0
- package/dist/src/render/blocks/pricing-table.js.map +1 -0
- package/dist/src/render/blocks/prose.d.ts +16 -0
- package/dist/src/render/blocks/prose.d.ts.map +1 -0
- package/dist/src/render/blocks/prose.js +17 -0
- package/dist/src/render/blocks/prose.js.map +1 -0
- package/dist/src/render/blocks/quote.d.ts +16 -0
- package/dist/src/render/blocks/quote.d.ts.map +1 -0
- package/dist/src/render/blocks/quote.js +29 -0
- package/dist/src/render/blocks/quote.js.map +1 -0
- package/dist/src/render/blocks/stat-counter.d.ts +4 -0
- package/dist/src/render/blocks/stat-counter.d.ts.map +1 -0
- package/dist/src/render/blocks/stat-counter.js +17 -0
- package/dist/src/render/blocks/stat-counter.js.map +1 -0
- package/dist/src/render/blocks/stats.d.ts +4 -0
- package/dist/src/render/blocks/stats.d.ts.map +1 -0
- package/dist/src/render/blocks/stats.js +19 -0
- package/dist/src/render/blocks/stats.js.map +1 -0
- package/dist/src/render/blocks/testimonial.d.ts +19 -0
- package/dist/src/render/blocks/testimonial.d.ts.map +1 -0
- package/dist/src/render/blocks/testimonial.js +28 -0
- package/dist/src/render/blocks/testimonial.js.map +1 -0
- package/dist/src/render/chrome.d.ts +3 -0
- package/dist/src/render/chrome.d.ts.map +1 -0
- package/dist/src/render/chrome.js +136 -0
- package/dist/src/render/chrome.js.map +1 -0
- package/dist/src/render/render-block.d.ts +21 -0
- package/dist/src/render/render-block.d.ts.map +1 -0
- package/dist/src/render/render-block.js +104 -0
- package/dist/src/render/render-block.js.map +1 -0
- package/dist/src/render/term-archive.d.ts +13 -0
- package/dist/src/render/term-archive.d.ts.map +1 -0
- package/dist/src/render/term-archive.js +35 -0
- package/dist/src/render/term-archive.js.map +1 -0
- package/dist/theme.config.d.ts +12 -0
- package/dist/theme.config.d.ts.map +1 -0
- package/dist/theme.config.js +42 -0
- package/dist/theme.config.js.map +1 -0
- package/package.json +57 -0
- package/src/index.ts +20 -0
- package/src/render/blocks/accordion.ts +51 -0
- package/src/render/blocks/collection-list.ts +286 -0
- package/src/render/blocks/cta.ts +33 -0
- package/src/render/blocks/embed.ts +99 -0
- package/src/render/blocks/faq.ts +58 -0
- package/src/render/blocks/feature-grid.ts +61 -0
- package/src/render/blocks/gallery.ts +46 -0
- package/src/render/blocks/hero.ts +54 -0
- package/src/render/blocks/logo-strip.ts +35 -0
- package/src/render/blocks/logos.ts +57 -0
- package/src/render/blocks/media-figure.ts +43 -0
- package/src/render/blocks/pricing-table.ts +76 -0
- package/src/render/blocks/prose.ts +22 -0
- package/src/render/blocks/quote.ts +46 -0
- package/src/render/blocks/stat-counter.ts +39 -0
- package/src/render/blocks/stats.ts +46 -0
- package/src/render/blocks/testimonial.ts +44 -0
- package/src/render/chrome.ts +162 -0
- package/src/render/render-block.ts +137 -0
- package/src/render/term-archive.ts +87 -0
- package/src/styles/archive.css +40 -0
- package/src/styles/base.css +1037 -0
- package/src/styles/blocks.css +1335 -0
- package/src/styles/theme.css +18 -0
- package/src/styles/tokens.css +308 -0
- package/tokens.json +36 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type BlockRegistry, VOCABULARY_NAMES, type VocabularyBlock } from '@cogenta/blocks'
|
|
2
|
+
import {
|
|
3
|
+
type FetchedEntries,
|
|
4
|
+
type HtmlElement,
|
|
5
|
+
h,
|
|
6
|
+
type PageContent,
|
|
7
|
+
pageHasOwnHeading,
|
|
8
|
+
type RenderContext,
|
|
9
|
+
renderEntryHeader,
|
|
10
|
+
resolveBlockForRender,
|
|
11
|
+
withBlockKey,
|
|
12
|
+
withBlockVariant,
|
|
13
|
+
} from '@cogenta/theme-kit'
|
|
14
|
+
import { renderAccordion } from './blocks/accordion.js'
|
|
15
|
+
import { renderCollectionList } from './blocks/collection-list.js'
|
|
16
|
+
import { renderCta } from './blocks/cta.js'
|
|
17
|
+
import { renderEmbed } from './blocks/embed.js'
|
|
18
|
+
import { renderFaq } from './blocks/faq.js'
|
|
19
|
+
import { renderFeatureGrid } from './blocks/feature-grid.js'
|
|
20
|
+
import { renderGallery } from './blocks/gallery.js'
|
|
21
|
+
import { renderHero } from './blocks/hero.js'
|
|
22
|
+
import { renderLogoStrip } from './blocks/logo-strip.js'
|
|
23
|
+
import { renderLogos } from './blocks/logos.js'
|
|
24
|
+
import { renderMediaFigure } from './blocks/media-figure.js'
|
|
25
|
+
import { renderPricingTable } from './blocks/pricing-table.js'
|
|
26
|
+
import { renderProse } from './blocks/prose.js'
|
|
27
|
+
import { renderQuote } from './blocks/quote.js'
|
|
28
|
+
import { renderStatCounter } from './blocks/stat-counter.js'
|
|
29
|
+
import { renderStats } from './blocks/stats.js'
|
|
30
|
+
import { renderTestimonial } from './blocks/testimonial.js'
|
|
31
|
+
|
|
32
|
+
export function renderBlock(
|
|
33
|
+
block: VocabularyBlock,
|
|
34
|
+
ctx: RenderContext,
|
|
35
|
+
entries: FetchedEntries = {},
|
|
36
|
+
registry?: BlockRegistry,
|
|
37
|
+
): HtmlElement | null {
|
|
38
|
+
// `block`'s type says `VocabularyBlock`, but the value crossing this
|
|
39
|
+
// boundary from stored content is not always literally one of the shared
|
|
40
|
+
// vocabulary — resolving here is what turns an unimplemented theme-private
|
|
41
|
+
// block into its declared fallback instead of a silently blank slot.
|
|
42
|
+
const resolved = resolveBlockForRender(block, VOCABULARY_NAMES, registry)
|
|
43
|
+
if (resolved === null) return null
|
|
44
|
+
const known = resolved as unknown as VocabularyBlock
|
|
45
|
+
// `withBlockVariant` (blocks@2.0, RFC 0002) is applied once here, after
|
|
46
|
+
// dispatch, rather than inside each of the seventeen block renderers —
|
|
47
|
+
// `variant` is envelope data every block carries identically, exactly the
|
|
48
|
+
// same reasoning `withBlockKey` in `renderPage` already follows for `_key`.
|
|
49
|
+
return withBlockVariant(renderKnownBlock(known, ctx, entries), known.variant)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function renderKnownBlock(
|
|
53
|
+
known: VocabularyBlock,
|
|
54
|
+
ctx: RenderContext,
|
|
55
|
+
entries: FetchedEntries,
|
|
56
|
+
): HtmlElement | null {
|
|
57
|
+
switch (known._type) {
|
|
58
|
+
case 'hero':
|
|
59
|
+
return renderHero(known, ctx)
|
|
60
|
+
case 'prose':
|
|
61
|
+
return renderProse(known, ctx)
|
|
62
|
+
case 'mediaFigure':
|
|
63
|
+
return renderMediaFigure(known, ctx)
|
|
64
|
+
case 'featureGrid':
|
|
65
|
+
return renderFeatureGrid(known, ctx)
|
|
66
|
+
case 'cta':
|
|
67
|
+
return renderCta(known, ctx)
|
|
68
|
+
case 'gallery':
|
|
69
|
+
return renderGallery(known, ctx)
|
|
70
|
+
case 'quote':
|
|
71
|
+
return renderQuote(known, ctx)
|
|
72
|
+
case 'faq':
|
|
73
|
+
return renderFaq(known, ctx)
|
|
74
|
+
case 'stats':
|
|
75
|
+
return renderStats(known, ctx)
|
|
76
|
+
case 'logos':
|
|
77
|
+
return renderLogos(known, ctx)
|
|
78
|
+
case 'collectionList':
|
|
79
|
+
return renderCollectionList(known, ctx, entries[known._key] ?? [])
|
|
80
|
+
case 'embed':
|
|
81
|
+
return renderEmbed(known, ctx)
|
|
82
|
+
case 'testimonial':
|
|
83
|
+
return renderTestimonial(known, ctx)
|
|
84
|
+
case 'pricingTable':
|
|
85
|
+
return renderPricingTable(known, ctx)
|
|
86
|
+
case 'accordion':
|
|
87
|
+
return renderAccordion(known, ctx)
|
|
88
|
+
case 'statCounter':
|
|
89
|
+
return renderStatCounter(known, ctx)
|
|
90
|
+
case 'logoStrip':
|
|
91
|
+
return renderLogoStrip(known, ctx)
|
|
92
|
+
default: {
|
|
93
|
+
// Exhaustive over contract B's vocabulary: `known` is `never` here, so
|
|
94
|
+
// a block this package does not implement stops it compiling until it
|
|
95
|
+
// is. `null`, not a throw — a theme has no `@cogenta/core` error types
|
|
96
|
+
// to reach for (contract D refuses that import), and a fallback block
|
|
97
|
+
// is the render layer's decision, not the theme's.
|
|
98
|
+
const unreachable: never = known
|
|
99
|
+
void unreachable
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Wraps every block in `<main id="cg-main">` — the skip link's anchor — and
|
|
107
|
+
* marks each rendered block with its own `_key` (`withBlockKey`, mandatory:
|
|
108
|
+
* it is what lets the L16 page builder map a clicked element on the real
|
|
109
|
+
* rendered page back to the block that produced it). A page whose blocks
|
|
110
|
+
* already carry a heading (a `hero`) gets no second `<h1>`.
|
|
111
|
+
*
|
|
112
|
+
* A page carrying `entry` (contract D `theme@1.4`) gets `renderEntryHeader`'s
|
|
113
|
+
* furniture — classification eyebrow (styled in this masthead's own
|
|
114
|
+
* journal-red accent, see `styles/base.css`'s `.cg-entry-header__terms`),
|
|
115
|
+
* title, excerpt, date/author/reading-time meta and cover — instead of the
|
|
116
|
+
* bare `<h1>` every other page falls back to; `renderEntryHeader` itself
|
|
117
|
+
* returns `null` for a `blocks`-only page (e.g. `about`, which carries no
|
|
118
|
+
* `entry`) or one that already draws its own heading, so the bare-title
|
|
119
|
+
* fallback still applies there, exactly as before.
|
|
120
|
+
*/
|
|
121
|
+
export function renderPage(
|
|
122
|
+
page: PageContent,
|
|
123
|
+
ctx: RenderContext,
|
|
124
|
+
entries: FetchedEntries = {},
|
|
125
|
+
registry?: BlockRegistry,
|
|
126
|
+
): HtmlElement {
|
|
127
|
+
const ownHeading = pageHasOwnHeading(page.blocks)
|
|
128
|
+
const entryHeader = ownHeading ? null : renderEntryHeader(page, ctx)
|
|
129
|
+
return h(
|
|
130
|
+
'main',
|
|
131
|
+
{ class: 'cg-main', id: 'cg-main' },
|
|
132
|
+
ownHeading ? null : (entryHeader ?? h('h1', { class: 'cg-page__title' }, page.title)),
|
|
133
|
+
page.blocks.map((block) =>
|
|
134
|
+
withBlockKey(renderBlock(block, ctx, entries, registry), block._key),
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type HtmlElement, h, type TermArchiveInput } from '@cogenta/theme-kit'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The taxonomy-term archive (contract D `theme@1.3`), as a section front.
|
|
5
|
+
*
|
|
6
|
+
* A magazine's archive is a section page, so it is built from the same
|
|
7
|
+
* `cg-issue__row` rows `collectionList` already sets here — a contents strip,
|
|
8
|
+
* not a card grid — under a section head that names the taxonomy as a kicker
|
|
9
|
+
* and the term as the section title. Reusing the block's classes is what
|
|
10
|
+
* keeps the two looking like the same publication without a second
|
|
11
|
+
* stylesheet.
|
|
12
|
+
*/
|
|
13
|
+
export function renderTermArchive(input: TermArchiveInput): HtmlElement {
|
|
14
|
+
const rows = input.entries.map((entry, index) =>
|
|
15
|
+
h(
|
|
16
|
+
'li',
|
|
17
|
+
{ class: 'cg-issue__row' },
|
|
18
|
+
h(
|
|
19
|
+
'span',
|
|
20
|
+
{ class: 'cg-issue__row-number', 'aria-hidden': 'true' },
|
|
21
|
+
String(index + 1).padStart(2, '0'),
|
|
22
|
+
),
|
|
23
|
+
h(
|
|
24
|
+
'div',
|
|
25
|
+
{ class: 'cg-issue__row-body' },
|
|
26
|
+
h(
|
|
27
|
+
'h2',
|
|
28
|
+
{ class: 'cg-issue__row-title' },
|
|
29
|
+
entry.href === null
|
|
30
|
+
? entry.title
|
|
31
|
+
: h('a', { class: 'cg-issue__link', href: entry.href }, entry.title),
|
|
32
|
+
),
|
|
33
|
+
entry.publishedAt === null
|
|
34
|
+
? null
|
|
35
|
+
: h(
|
|
36
|
+
'time',
|
|
37
|
+
{ class: 'cg-issue__row-date', datetime: entry.publishedAt },
|
|
38
|
+
entry.publishedAt.slice(0, 10),
|
|
39
|
+
),
|
|
40
|
+
entry.summary === null ? null : h('p', { class: 'cg-issue__lead-excerpt' }, entry.summary),
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return h(
|
|
46
|
+
'main',
|
|
47
|
+
{ class: 'cg-main cg-archive', id: 'cg-main' },
|
|
48
|
+
input.ancestors.length === 0
|
|
49
|
+
? null
|
|
50
|
+
: h(
|
|
51
|
+
'nav',
|
|
52
|
+
{ class: 'cg-archive__breadcrumb', 'aria-label': input.labels.breadcrumb },
|
|
53
|
+
h(
|
|
54
|
+
'ol',
|
|
55
|
+
{},
|
|
56
|
+
...input.ancestors.map((l) => h('li', {}, h('a', { href: l.href }, l.label))),
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
h('p', { class: 'cg-issue__kicker' }, input.taxonomyName),
|
|
60
|
+
h('h1', { class: 'cg-page__title' }, input.term.label),
|
|
61
|
+
input.children.length === 0
|
|
62
|
+
? null
|
|
63
|
+
: h(
|
|
64
|
+
'ul',
|
|
65
|
+
{ class: 'cg-archive__children', 'aria-label': input.labels.subterms },
|
|
66
|
+
...input.children.map((c) => h('li', {}, h('a', { href: c.href }, c.label))),
|
|
67
|
+
),
|
|
68
|
+
rows.length === 0
|
|
69
|
+
? h('p', { class: 'cg-issue__empty' }, input.labels.empty)
|
|
70
|
+
: h('ul', { class: 'cg-issue__rest' }, ...rows),
|
|
71
|
+
pager(input),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function pager(input: TermArchiveInput): HtmlElement | null {
|
|
76
|
+
if (input.page.previousHref === null && input.page.nextHref === null) return null
|
|
77
|
+
return h(
|
|
78
|
+
'nav',
|
|
79
|
+
{ class: 'cg-archive__pager', 'aria-label': input.labels.pagination },
|
|
80
|
+
input.page.previousHref === null
|
|
81
|
+
? null
|
|
82
|
+
: h('a', { rel: 'prev', href: input.page.previousHref }, input.labels.previous),
|
|
83
|
+
input.page.nextHref === null
|
|
84
|
+
? null
|
|
85
|
+
: h('a', { rel: 'next', href: input.page.nextHref }, input.labels.next),
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The taxonomy-term archive (contract D `theme@1.3`).
|
|
3
|
+
*
|
|
4
|
+
* Only the three things an archive adds beyond what `collectionList` already
|
|
5
|
+
* styles — a breadcrumb, a sub-term row, a prev/next pager. The entry list
|
|
6
|
+
* itself reuses the `cg-entry`/`cg-collection__items` rules, on purpose: an
|
|
7
|
+
* archive of articles must look like this theme's list of articles, and a
|
|
8
|
+
* second set of card rules is exactly how those two drift apart.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.cg-archive {
|
|
12
|
+
max-inline-size: var(--cg-page);
|
|
13
|
+
margin-inline: auto;
|
|
14
|
+
padding: var(--cg-s10) var(--cg-gutter);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.cg-archive__breadcrumb ol,
|
|
18
|
+
.cg-archive__children {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
gap: var(--cg-s3);
|
|
22
|
+
margin: 0 0 var(--cg-s4);
|
|
23
|
+
padding: 0;
|
|
24
|
+
list-style: none;
|
|
25
|
+
font-size: var(--cg-text-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.cg-archive__children {
|
|
29
|
+
margin-block: var(--cg-s4) var(--cg-s8);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.cg-archive__pager {
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
gap: var(--cg-s4);
|
|
36
|
+
margin-block-start: var(--cg-s10);
|
|
37
|
+
padding-block-start: var(--cg-s5);
|
|
38
|
+
border-block-start: 1px solid var(--cg-rule-soft);
|
|
39
|
+
font-weight: var(--cg-weight-semibold);
|
|
40
|
+
}
|