@conduction/docusaurus-preset 0.1.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.
Files changed (163) hide show
  1. package/MISSING_COMPONENTS.md +109 -0
  2. package/README.md +171 -0
  3. package/package.json +59 -0
  4. package/src/components/AgentTrace/AgentTrace.jsx +128 -0
  5. package/src/components/AgentTrace/AgentTrace.module.css +115 -0
  6. package/src/components/AppMock/AppMock.jsx +86 -0
  7. package/src/components/AppMock/AppMock.module.css +629 -0
  8. package/src/components/AppMock/variants/DeciDeskMock.jsx +71 -0
  9. package/src/components/AppMock/variants/DocuDeskMock.jsx +69 -0
  10. package/src/components/AppMock/variants/LarpingAppMock.jsx +59 -0
  11. package/src/components/AppMock/variants/MyDashBiMock.jsx +135 -0
  12. package/src/components/AppMock/variants/MyDashMock.jsx +96 -0
  13. package/src/components/AppMock/variants/MyDashTilesMock.jsx +103 -0
  14. package/src/components/AppMock/variants/MyDashWidgetsMock.jsx +123 -0
  15. package/src/components/AppMock/variants/NLDesignMock.jsx +70 -0
  16. package/src/components/AppMock/variants/OpenCatalogiMock.jsx +61 -0
  17. package/src/components/AppMock/variants/OpenConnectorMock.jsx +83 -0
  18. package/src/components/AppMock/variants/OpenRegisterMock.jsx +100 -0
  19. package/src/components/AppMock/variants/OpenWooMock.jsx +61 -0
  20. package/src/components/AppMock/variants/PipelinQMock.jsx +88 -0
  21. package/src/components/AppMock/variants/ProcestMock.jsx +87 -0
  22. package/src/components/AppMock/variants/SoftwareCatalogMock.jsx +71 -0
  23. package/src/components/AppMock/variants/ZaakAfhandelAppMock.jsx +71 -0
  24. package/src/components/AppsGrid/AppsGrid.jsx +84 -0
  25. package/src/components/AppsGrid/AppsGrid.module.css +46 -0
  26. package/src/components/AppsPreview/AppsPreview.jsx +85 -0
  27. package/src/components/AppsPreview/AppsPreview.module.css +128 -0
  28. package/src/components/Clients/Clients.jsx +205 -0
  29. package/src/components/Clients/Clients.module.css +166 -0
  30. package/src/components/ComposeBlock/ComposeBlock.jsx +70 -0
  31. package/src/components/ComposeBlock/ComposeBlock.module.css +74 -0
  32. package/src/components/ConductionBg/ConductionBg.jsx +150 -0
  33. package/src/components/ConductionBg/ConductionBg.module.css +41 -0
  34. package/src/components/ContentCard/ContentCard.jsx +126 -0
  35. package/src/components/ContentCard/ContentCard.module.css +84 -0
  36. package/src/components/ContentDetailHero/ContentDetailHero.jsx +136 -0
  37. package/src/components/ContentDetailHero/ContentDetailHero.module.css +96 -0
  38. package/src/components/ContentTypeFilter/ContentTypeFilter.jsx +103 -0
  39. package/src/components/ContentTypeFilter/ContentTypeFilter.module.css +60 -0
  40. package/src/components/ContentTypeFilter/contentTypes.js +58 -0
  41. package/src/components/CookieCli/CookieCli.jsx +223 -0
  42. package/src/components/CookieCli/CookieCli.module.css +166 -0
  43. package/src/components/CtaBanner/CtaBanner.jsx +61 -0
  44. package/src/components/CtaBanner/CtaBanner.module.css +65 -0
  45. package/src/components/DetailHero/DetailHero.jsx +143 -0
  46. package/src/components/DetailHero/DetailHero.module.css +154 -0
  47. package/src/components/Diagrams/Diagrams.jsx +148 -0
  48. package/src/components/EmployeeCard/EmployeeCard.jsx +127 -0
  49. package/src/components/EmployeeCard/EmployeeCard.module.css +144 -0
  50. package/src/components/ExternalAppShelf/ExternalAppShelf.jsx +61 -0
  51. package/src/components/ExternalAppShelf/ExternalAppShelf.module.css +90 -0
  52. package/src/components/FAQ/FAQ.jsx +42 -0
  53. package/src/components/FAQ/FAQ.module.css +74 -0
  54. package/src/components/FacetedFilters/FacetedFilters.jsx +125 -0
  55. package/src/components/FacetedFilters/FacetedFilters.module.css +133 -0
  56. package/src/components/FeatureGrid/FeatureGrid.jsx +94 -0
  57. package/src/components/FeatureGrid/FeatureGrid.module.css +114 -0
  58. package/src/components/FeatureList/FeatureList.jsx +54 -0
  59. package/src/components/FeatureList/FeatureList.module.css +52 -0
  60. package/src/components/FeaturedCard/FeaturedCard.jsx +101 -0
  61. package/src/components/FeaturedCard/FeaturedCard.module.css +98 -0
  62. package/src/components/GameModal/GameModal.jsx +197 -0
  63. package/src/components/GameModal/GameModal.module.css +184 -0
  64. package/src/components/Hero/Hero.jsx +101 -0
  65. package/src/components/Hero/Hero.module.css +95 -0
  66. package/src/components/HexBackground/HexBackground.jsx +56 -0
  67. package/src/components/HexBackground/HexBackground.module.css +73 -0
  68. package/src/components/HexNetwork/HexNetwork.jsx +141 -0
  69. package/src/components/HexNetwork/HexNetwork.module.css +187 -0
  70. package/src/components/HexRain/HexRain.jsx +81 -0
  71. package/src/components/HowSteps/HowSteps.jsx +57 -0
  72. package/src/components/HowSteps/HowSteps.module.css +52 -0
  73. package/src/components/ManagedCommonGround/ManagedCommonGround.jsx +78 -0
  74. package/src/components/ManagedCommonGround/ManagedCommonGround.module.css +16 -0
  75. package/src/components/NewsletterCta/NewsletterCta.jsx +83 -0
  76. package/src/components/NewsletterCta/NewsletterCta.module.css +103 -0
  77. package/src/components/PairCard/PairCard.jsx +58 -0
  78. package/src/components/PairCard/PairCard.module.css +54 -0
  79. package/src/components/PartnerCard/PartnerCard.jsx +130 -0
  80. package/src/components/PartnerCard/PartnerCard.module.css +198 -0
  81. package/src/components/PartnerDirectory/PartnerDirectory.jsx +122 -0
  82. package/src/components/PartnerDirectory/PartnerDirectory.module.css +25 -0
  83. package/src/components/PartnerSidecard/PartnerSidecard.jsx +116 -0
  84. package/src/components/PartnerSidecard/PartnerSidecard.module.css +185 -0
  85. package/src/components/Pipeline/Pipeline.jsx +198 -0
  86. package/src/components/Pipeline/Pipeline.module.css +206 -0
  87. package/src/components/PlatformDiagram/PlatformDiagram.jsx +110 -0
  88. package/src/components/PlatformOverview/PlatformOverview.jsx +68 -0
  89. package/src/components/PlatformOverview/PlatformOverview.module.css +71 -0
  90. package/src/components/ReferenceCard/ReferenceCard.jsx +44 -0
  91. package/src/components/ReferenceCard/ReferenceCard.module.css +57 -0
  92. package/src/components/RelatedPosts/RelatedPosts.jsx +58 -0
  93. package/src/components/RelatedPosts/RelatedPosts.module.css +51 -0
  94. package/src/components/RotatingCards/RotatingCards.jsx +98 -0
  95. package/src/components/RotatingCards/RotatingCards.module.css +153 -0
  96. package/src/components/Showcase/Showcase.jsx +129 -0
  97. package/src/components/Showcase/Showcase.module.css +168 -0
  98. package/src/components/SolutionCard/SolutionCard.jsx +83 -0
  99. package/src/components/SolutionCard/SolutionCard.module.css +99 -0
  100. package/src/components/StatsStrip/StatsStrip.jsx +38 -0
  101. package/src/components/StatsStrip/StatsStrip.module.css +53 -0
  102. package/src/components/WidgetShelf/WidgetShelf.jsx +67 -0
  103. package/src/components/WidgetShelf/WidgetShelf.module.css +73 -0
  104. package/src/components/index.js +96 -0
  105. package/src/components/primitives/AuthorByline.jsx +85 -0
  106. package/src/components/primitives/AuthorByline.module.css +57 -0
  107. package/src/components/primitives/BrandCitation.jsx +71 -0
  108. package/src/components/primitives/Button.jsx +46 -0
  109. package/src/components/primitives/Button.module.css +88 -0
  110. package/src/components/primitives/Card.jsx +42 -0
  111. package/src/components/primitives/Card.module.css +42 -0
  112. package/src/components/primitives/Eyebrow.jsx +37 -0
  113. package/src/components/primitives/Eyebrow.module.css +19 -0
  114. package/src/components/primitives/HexBullet.jsx +37 -0
  115. package/src/components/primitives/HexBullet.module.css +16 -0
  116. package/src/components/primitives/HexThumbnail.jsx +70 -0
  117. package/src/components/primitives/HexThumbnail.module.css +45 -0
  118. package/src/components/primitives/Pill.jsx +42 -0
  119. package/src/components/primitives/Pill.module.css +30 -0
  120. package/src/components/primitives/Section.jsx +51 -0
  121. package/src/components/primitives/Section.module.css +31 -0
  122. package/src/components/primitives/SectionHead.jsx +36 -0
  123. package/src/components/primitives/SectionHead.module.css +43 -0
  124. package/src/components/primitives/index.js +22 -0
  125. package/src/css/brand.css +158 -0
  126. package/src/css/tokens.css +12 -0
  127. package/src/data/app-downloads.js +42 -0
  128. package/src/diagrams/README.md +74 -0
  129. package/src/diagrams/cn-domain-tree.js +105 -0
  130. package/src/diagrams/cn-hex-prism.js +163 -0
  131. package/src/diagrams/cn-hex.js +181 -0
  132. package/src/diagrams/cn-honeycomb-bg.js +135 -0
  133. package/src/diagrams/cn-pipeline.js +150 -0
  134. package/src/diagrams/cn-platform.js +156 -0
  135. package/src/diagrams/cn-side-box.js +104 -0
  136. package/src/diagrams/index.js +28 -0
  137. package/src/index.js +183 -0
  138. package/src/theme/Footer/index.jsx +516 -0
  139. package/src/theme/MDXPage/index.jsx +134 -0
  140. package/src/theme/Navbar/index.jsx +120 -0
  141. package/src/theme/Navbar/styles.module.css +114 -0
  142. package/src/theme/brand.jsx +63 -0
  143. package/src/theme.js +45 -0
  144. package/src/utils/lazyScript.js +37 -0
  145. package/static/img/favicon.svg +14 -0
  146. package/static/img/honeycomb-scatter.svg +23 -0
  147. package/static/img/honeycomb-watermark.svg +108 -0
  148. package/static/img/logo-dark.svg +11 -0
  149. package/static/img/logo.svg +14 -0
  150. package/static/img/nextcloud-logo.svg +5 -0
  151. package/static/lib/canal-footer.css +418 -0
  152. package/static/lib/canal-footer.js +499 -0
  153. package/static/lib/clients-flow.js +317 -0
  154. package/static/lib/conduction-bg.css +50 -0
  155. package/static/lib/conduction-bg.js +122 -0
  156. package/static/lib/hex-rain.css +128 -0
  157. package/static/lib/hex-rain.js +284 -0
  158. package/static/lib/kade-cyclist.css +264 -0
  159. package/static/lib/kade-cyclist.js +420 -0
  160. package/static/lib/logo-memory.css +219 -0
  161. package/static/lib/logo-memory.js +540 -0
  162. package/static/lib/platform-diagram.css +458 -0
  163. package/static/lib/platform-diagram.js +414 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * <ContentCard /> styles. Mirrors .content-card and .content-grid in
3
+ * preview/components/academy.css.
4
+ */
5
+
6
+ .grid { display: grid; gap: var(--space-6); }
7
+ .cols-1 { grid-template-columns: 1fr; }
8
+ .cols-2 { grid-template-columns: repeat(2, 1fr); }
9
+ .cols-3 { grid-template-columns: repeat(3, 1fr); }
10
+ @media (max-width: 900px) {
11
+ .cols-2, .cols-3 { grid-template-columns: 1fr; }
12
+ }
13
+
14
+ .card {
15
+ display: grid;
16
+ grid-template-columns: 200px 1fr;
17
+ gap: var(--space-6);
18
+ padding: var(--space-5);
19
+ background: var(--c-cobalt-50);
20
+ border-radius: var(--radius-lg);
21
+ text-decoration: none;
22
+ color: inherit;
23
+ transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
24
+ align-items: center;
25
+ font-family: var(--conduction-typography-font-family-body);
26
+ }
27
+ .card:hover {
28
+ background: white;
29
+ box-shadow: var(--shadow-2);
30
+ transform: translateY(-2px);
31
+ text-decoration: none;
32
+ color: inherit;
33
+ }
34
+
35
+ @media (max-width: 700px) {
36
+ .card { grid-template-columns: 1fr; }
37
+ }
38
+
39
+ .thumbPanel {
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ width: 100%;
44
+ height: 184px;
45
+ border-radius: var(--radius-md);
46
+ position: relative;
47
+ overflow: hidden;
48
+ }
49
+ .panel-cobalt { background: var(--c-blue-cobalt); }
50
+ .panel-cobalt-dark { background: var(--c-cobalt-700); }
51
+ .panel-cobalt-deep { background: var(--c-cobalt-900); }
52
+ .panel-mint { background: var(--c-mint-500); }
53
+ .panel-orange { background: var(--c-orange-knvb); }
54
+ .panel-cobalt-50 { background: var(--c-cobalt-50); }
55
+
56
+ .body {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: var(--space-3);
60
+ min-width: 0;
61
+ }
62
+
63
+ .title {
64
+ font-size: 18px;
65
+ font-weight: 700;
66
+ letter-spacing: -0.01em;
67
+ color: var(--c-cobalt-900);
68
+ margin: 0;
69
+ line-height: 1.3;
70
+ }
71
+
72
+ .summary {
73
+ font-size: 14px;
74
+ color: var(--c-cobalt-700);
75
+ margin: 0;
76
+ line-height: 1.55;
77
+ }
78
+
79
+ .tags {
80
+ display: flex;
81
+ gap: 6px;
82
+ flex-wrap: wrap;
83
+ margin-top: 4px;
84
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * <ContentDetailHero />
3
+ *
4
+ * Header for individual academy posts. Crumb, content-type chip plus
5
+ * topical tags, h1 title, optional summary, author byline, optional
6
+ * duration label, then a 16:9 cover region with the honeycomb
7
+ * watermark and either a hex-thumb icon or a hero image inside.
8
+ *
9
+ * Distinct from <DetailHero /> (which is for app/solution/partner
10
+ * pages and has the 360px cobalt hex on the right). Academy detail
11
+ * pages want a wider headline and a full-width cover image; this
12
+ * component is built for that.
13
+ *
14
+ * Mirrors the .content-detail-hero section in
15
+ * preview/components/academy.html.
16
+ *
17
+ * Usage:
18
+ *
19
+ * <ContentDetailHero
20
+ * crumb={[{label: 'Academy', href: '/'}, {label: 'Guides', href: '/?type=guide'}, 'Install OpenCatalogi']}
21
+ * contentType="guide"
22
+ * tags={['OpenCatalogi', 'Install']}
23
+ * title="Install OpenCatalogi in two minutes."
24
+ * summary="From app store to first register, no terminal required."
25
+ * author={{ name: 'Ruben van der Linde' }}
26
+ * date="2026-05-05"
27
+ * duration="12 min read"
28
+ * cover={{ src: '/img/posts/install-opencatalogi.jpg', alt: 'Install screen' }}
29
+ * />
30
+ *
31
+ * If `cover` is omitted, the cover region renders a single large
32
+ * pointy-top hex on the cobalt-900 ground. Pass `cover.icon` for a
33
+ * custom SVG instead of the default placeholder.
34
+ */
35
+
36
+ import React from 'react';
37
+ import HexThumbnail from '../primitives/HexThumbnail';
38
+ import AuthorByline from '../primitives/AuthorByline';
39
+ import Pill from '../primitives/Pill';
40
+ import {
41
+ CONTENT_TYPE_LABELS,
42
+ CONTENT_TYPE_BULLET_COLOR,
43
+ } from '../ContentTypeFilter/contentTypes';
44
+ import styles from './ContentDetailHero.module.css';
45
+
46
+ export default function ContentDetailHero({
47
+ crumb,
48
+ contentType,
49
+ contentTypeLabel,
50
+ tags = [],
51
+ title,
52
+ summary,
53
+ author,
54
+ date,
55
+ dateLabel,
56
+ duration,
57
+ locale,
58
+ cover,
59
+ className,
60
+ }) {
61
+ const composed = [styles.hero, className].filter(Boolean).join(' ');
62
+
63
+ const typeLabel = contentTypeLabel
64
+ || (contentType && CONTENT_TYPE_LABELS[contentType])
65
+ || null;
66
+ const typeBullet = contentType
67
+ ? CONTENT_TYPE_BULLET_COLOR[contentType]
68
+ : undefined;
69
+
70
+ return (
71
+ <section className={composed}>
72
+ {Array.isArray(crumb) && crumb.length > 0 && (
73
+ <div className={styles.crumb}>
74
+ {crumb.map((c, i) => {
75
+ const last = i === crumb.length - 1;
76
+ const sep = !last
77
+ ? <span className={styles.sep} aria-hidden="true">/</span>
78
+ : null;
79
+ if (typeof c === 'string') {
80
+ return <React.Fragment key={i}>{c}{sep}</React.Fragment>;
81
+ }
82
+ return (
83
+ <React.Fragment key={i}>
84
+ {c.href
85
+ ? <a href={c.href}>{c.label}</a>
86
+ : <span>{c.label}</span>}
87
+ {sep}
88
+ </React.Fragment>
89
+ );
90
+ })}
91
+ </div>
92
+ )}
93
+
94
+ {(typeLabel || tags.length > 0) && (
95
+ <div className={styles.tags}>
96
+ {typeLabel && (
97
+ <Pill bullet bulletColor={typeBullet}>{typeLabel}</Pill>
98
+ )}
99
+ {tags.map((t, i) => (
100
+ <Pill key={i} bullet bulletColor="var(--c-cobalt-300)">{t}</Pill>
101
+ ))}
102
+ </div>
103
+ )}
104
+
105
+ {title && <h1 className={styles.title}>{title}</h1>}
106
+ {summary && <p className={styles.summary}>{summary}</p>}
107
+
108
+ {(author || date || duration) && (
109
+ <div className={styles.meta}>
110
+ {(author || date) && (
111
+ <AuthorByline
112
+ name={author && author.name}
113
+ avatarSrc={author && author.avatarSrc}
114
+ initials={author && author.initials}
115
+ date={date}
116
+ dateLabel={dateLabel}
117
+ locale={locale}
118
+ />
119
+ )}
120
+ {duration && <span className={styles.duration}>{duration}</span>}
121
+ </div>
122
+ )}
123
+
124
+ <div className={styles.cover}>
125
+ <span className={styles.watermark} aria-hidden="true" />
126
+ {cover && cover.src
127
+ ? <img src={cover.src} alt={cover.alt || ''} className={styles.coverImg} />
128
+ : (
129
+ <HexThumbnail size="xl" tone={cover && cover.tone || 'cobalt'}>
130
+ {cover && cover.icon}
131
+ </HexThumbnail>
132
+ )}
133
+ </div>
134
+ </section>
135
+ );
136
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * <ContentDetailHero /> styles. Mirrors .content-detail-hero in
3
+ * preview/components/academy.css.
4
+ */
5
+
6
+ .hero {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--space-6);
10
+ padding: var(--space-12) 0 var(--space-8);
11
+ font-family: var(--conduction-typography-font-family-body);
12
+ }
13
+
14
+ .crumb {
15
+ display: flex;
16
+ gap: 6px;
17
+ font-family: var(--conduction-typography-font-family-code);
18
+ font-size: 12px;
19
+ letter-spacing: 0.04em;
20
+ color: var(--c-cobalt-400);
21
+ flex-wrap: wrap;
22
+ }
23
+ .crumb a {
24
+ color: var(--c-cobalt-700);
25
+ text-decoration: none;
26
+ }
27
+ .crumb a:hover { color: var(--c-blue-cobalt); }
28
+ .crumb .sep { color: var(--c-cobalt-300); }
29
+
30
+ .tags {
31
+ display: flex;
32
+ gap: 6px;
33
+ flex-wrap: wrap;
34
+ }
35
+
36
+ .title {
37
+ font-size: 44px;
38
+ font-weight: 700;
39
+ letter-spacing: -0.02em;
40
+ color: var(--c-cobalt-900);
41
+ margin: 0;
42
+ line-height: 1.1;
43
+ max-width: 24ch;
44
+ }
45
+ @media (max-width: 700px) { .title { font-size: 32px; } }
46
+
47
+ .summary {
48
+ font-size: 18px;
49
+ color: var(--c-cobalt-700);
50
+ line-height: 1.55;
51
+ margin: 0;
52
+ max-width: 60ch;
53
+ }
54
+
55
+ .meta {
56
+ display: flex;
57
+ gap: var(--space-4);
58
+ align-items: center;
59
+ flex-wrap: wrap;
60
+ }
61
+
62
+ .duration {
63
+ font-family: var(--conduction-typography-font-family-code);
64
+ font-size: 12px;
65
+ letter-spacing: 0.04em;
66
+ color: var(--c-cobalt-400);
67
+ }
68
+
69
+ .cover {
70
+ width: 100%;
71
+ aspect-ratio: 16 / 9;
72
+ background: var(--c-cobalt-900);
73
+ border-radius: var(--radius-lg);
74
+ margin-top: var(--space-4);
75
+ position: relative;
76
+ overflow: hidden;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ }
81
+
82
+ .coverImg {
83
+ width: 100%;
84
+ height: 100%;
85
+ object-fit: cover;
86
+ }
87
+
88
+ .watermark {
89
+ position: absolute;
90
+ inset: 0;
91
+ pointer-events: none;
92
+ background-image: url('/img/honeycomb-scatter.svg');
93
+ background-size: 320px;
94
+ background-repeat: repeat;
95
+ opacity: 0.5;
96
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * <ContentTypeFilter />
3
+ *
4
+ * Top-of-page chip row that filters academy content by type. "All" is
5
+ * the default chip and is always present. Driven by a `?type=` query
6
+ * parameter so the filter survives reload and copy-paste.
7
+ *
8
+ * Mirrors the chip row in preview/components/academy.html.
9
+ *
10
+ * Modes:
11
+ * - controlled: pass `value` (string | null) and `onChange` (fn)
12
+ * - uncontrolled link mode: pass `hrefForType` (fn) to render <a>
13
+ * chips with hrefs and let the surrounding page re-render on
14
+ * navigation. Useful for static-site filtering via query string.
15
+ *
16
+ * Counts: pass `counts` keyed by type ({ blog: 18, guide: 9, ... })
17
+ * to render a small monospaced count next to each label. Pass a number
18
+ * to `allCount` for the "Everything" chip total. Counts are optional.
19
+ *
20
+ * Usage in MDX (uncontrolled, query-string driven):
21
+ *
22
+ * <ContentTypeFilter
23
+ * value={type}
24
+ * hrefForType={(t) => t ? `?type=${t}` : '?'}
25
+ * counts={{ blog: 18, guide: 9, 'case-study': 6, webinar: 5, tutorial: 4 }}
26
+ * />
27
+ *
28
+ * Usage in JSX (controlled):
29
+ *
30
+ * const [type, setType] = useState(null);
31
+ * <ContentTypeFilter value={type} onChange={setType} />
32
+ */
33
+
34
+ import React from 'react';
35
+ import {
36
+ CONTENT_TYPES,
37
+ CONTENT_TYPE_PLURAL_LABELS,
38
+ } from './contentTypes';
39
+ import styles from './ContentTypeFilter.module.css';
40
+
41
+ const ALL = '__all__';
42
+
43
+ export default function ContentTypeFilter({
44
+ value = null,
45
+ onChange,
46
+ hrefForType,
47
+ counts,
48
+ allLabel = 'Everything',
49
+ allCount,
50
+ types = CONTENT_TYPES,
51
+ className,
52
+ }) {
53
+ const isLinkMode = typeof hrefForType === 'function';
54
+
55
+ const renderChip = (key, label, count) => {
56
+ const active = (key === ALL && value == null) || key === value;
57
+ const composed = [
58
+ styles.chip,
59
+ active ? styles.active : null,
60
+ ].filter(Boolean).join(' ');
61
+
62
+ if (isLinkMode) {
63
+ return (
64
+ <a
65
+ key={key}
66
+ href={hrefForType(key === ALL ? null : key)}
67
+ className={composed}
68
+ >
69
+ <span className={styles.label}>{label}</span>
70
+ {typeof count === 'number' && (
71
+ <span className={styles.count}>{count}</span>
72
+ )}
73
+ </a>
74
+ );
75
+ }
76
+
77
+ return (
78
+ <button
79
+ key={key}
80
+ type="button"
81
+ className={composed}
82
+ onClick={() => onChange && onChange(key === ALL ? null : key)}
83
+ aria-pressed={active}
84
+ >
85
+ <span className={styles.label}>{label}</span>
86
+ {typeof count === 'number' && (
87
+ <span className={styles.count}>{count}</span>
88
+ )}
89
+ </button>
90
+ );
91
+ };
92
+
93
+ return (
94
+ <div className={[styles.row, className].filter(Boolean).join(' ')}>
95
+ {renderChip(ALL, allLabel, allCount)}
96
+ {types.map((t) =>
97
+ renderChip(t, CONTENT_TYPE_PLURAL_LABELS[t] || t, counts && counts[t])
98
+ )}
99
+ </div>
100
+ );
101
+ }
102
+
103
+ export {CONTENT_TYPES, CONTENT_TYPE_PLURAL_LABELS} from './contentTypes';
@@ -0,0 +1,60 @@
1
+ /**
2
+ * <ContentTypeFilter /> styles. Mirrors .type-filter in
3
+ * preview/components/academy.css.
4
+ */
5
+
6
+ .row {
7
+ display: flex;
8
+ gap: 8px;
9
+ flex-wrap: wrap;
10
+ align-items: center;
11
+ }
12
+
13
+ .chip {
14
+ display: inline-flex;
15
+ align-items: center;
16
+ gap: 6px;
17
+ padding: 6px 14px;
18
+ border-radius: var(--radius-pill);
19
+ background: white;
20
+ border: 1px solid var(--c-cobalt-100);
21
+ color: var(--c-cobalt-700);
22
+ font-family: var(--conduction-typography-font-family-body);
23
+ font-size: 13px;
24
+ font-weight: 500;
25
+ line-height: 1.4;
26
+ cursor: pointer;
27
+ transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
28
+ text-decoration: none;
29
+ }
30
+ .chip:hover {
31
+ background: var(--c-cobalt-50);
32
+ border-color: var(--c-cobalt-200);
33
+ text-decoration: none;
34
+ color: var(--c-cobalt-700);
35
+ }
36
+ .chip:focus-visible {
37
+ outline: 2px solid var(--c-blue-cobalt);
38
+ outline-offset: 2px;
39
+ }
40
+
41
+ .chip.active {
42
+ background: var(--c-blue-cobalt);
43
+ border-color: var(--c-blue-cobalt);
44
+ color: white;
45
+ }
46
+ .chip.active:hover {
47
+ background: var(--c-cobalt-700);
48
+ border-color: var(--c-cobalt-700);
49
+ color: white;
50
+ }
51
+
52
+ .label { display: inline-block; }
53
+
54
+ .count {
55
+ font-family: var(--conduction-typography-font-family-code);
56
+ font-size: 11px;
57
+ color: var(--c-cobalt-400);
58
+ font-weight: 500;
59
+ }
60
+ .chip.active .count { color: var(--c-cobalt-200); }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Single source of truth for the academy content-type taxonomy.
3
+ *
4
+ * Used by:
5
+ * - <ContentTypeFilter /> for the chip row
6
+ * - <ContentCard /> to render the type chip and pick a bullet colour
7
+ * - <ContentDetailHero /> to render the type chip in the header
8
+ * - schemas/academy/content.schema.json (mirrored in the JSON Schema)
9
+ *
10
+ * Content types correspond 1:1 with frontmatter `contentType:` values
11
+ * in academy MDX files. Extending this list here is enough to surface
12
+ * a new chip across every site that consumes the preset; the JSON
13
+ * Schema in /schemas/academy/content.schema.json must be updated in
14
+ * lockstep.
15
+ */
16
+
17
+ export const CONTENT_TYPES = [
18
+ 'blog',
19
+ 'guide',
20
+ 'case-study',
21
+ 'webinar',
22
+ 'tutorial',
23
+ ];
24
+
25
+ export const CONTENT_TYPE_LABELS = {
26
+ 'blog': 'Blog',
27
+ 'guide': 'Guide',
28
+ 'case-study': 'Case study',
29
+ 'webinar': 'Webinar',
30
+ 'tutorial': 'Tutorial',
31
+ };
32
+
33
+ /**
34
+ * Plural, sentence-case labels for the chip row. We use plural for
35
+ * the filter ("Blogs", "Case studies") because each chip filters a
36
+ * collection.
37
+ */
38
+ export const CONTENT_TYPE_PLURAL_LABELS = {
39
+ 'blog': 'Blogs',
40
+ 'guide': 'Guides',
41
+ 'case-study': 'Case studies',
42
+ 'webinar': 'Webinars',
43
+ 'tutorial': 'Tutorials',
44
+ };
45
+
46
+ /**
47
+ * Bullet (HexBullet) colour for the type chip. One token per type so
48
+ * a card's content-type pill is identifiable at a glance. Only one
49
+ * type uses KNVB orange (webinar), so the kit's "one orange accent
50
+ * per screen" rule still holds at landing scale.
51
+ */
52
+ export const CONTENT_TYPE_BULLET_COLOR = {
53
+ 'blog': 'var(--c-blue-cobalt)',
54
+ 'guide': 'var(--c-mint-500)',
55
+ 'case-study': 'var(--c-cobalt-700)',
56
+ 'webinar': 'var(--c-orange-knvb)',
57
+ 'tutorial': 'var(--c-cobalt-400)',
58
+ };