@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.
- package/MISSING_COMPONENTS.md +109 -0
- package/README.md +171 -0
- package/package.json +59 -0
- package/src/components/AgentTrace/AgentTrace.jsx +128 -0
- package/src/components/AgentTrace/AgentTrace.module.css +115 -0
- package/src/components/AppMock/AppMock.jsx +86 -0
- package/src/components/AppMock/AppMock.module.css +629 -0
- package/src/components/AppMock/variants/DeciDeskMock.jsx +71 -0
- package/src/components/AppMock/variants/DocuDeskMock.jsx +69 -0
- package/src/components/AppMock/variants/LarpingAppMock.jsx +59 -0
- package/src/components/AppMock/variants/MyDashBiMock.jsx +135 -0
- package/src/components/AppMock/variants/MyDashMock.jsx +96 -0
- package/src/components/AppMock/variants/MyDashTilesMock.jsx +103 -0
- package/src/components/AppMock/variants/MyDashWidgetsMock.jsx +123 -0
- package/src/components/AppMock/variants/NLDesignMock.jsx +70 -0
- package/src/components/AppMock/variants/OpenCatalogiMock.jsx +61 -0
- package/src/components/AppMock/variants/OpenConnectorMock.jsx +83 -0
- package/src/components/AppMock/variants/OpenRegisterMock.jsx +100 -0
- package/src/components/AppMock/variants/OpenWooMock.jsx +61 -0
- package/src/components/AppMock/variants/PipelinQMock.jsx +88 -0
- package/src/components/AppMock/variants/ProcestMock.jsx +87 -0
- package/src/components/AppMock/variants/SoftwareCatalogMock.jsx +71 -0
- package/src/components/AppMock/variants/ZaakAfhandelAppMock.jsx +71 -0
- package/src/components/AppsGrid/AppsGrid.jsx +84 -0
- package/src/components/AppsGrid/AppsGrid.module.css +46 -0
- package/src/components/AppsPreview/AppsPreview.jsx +85 -0
- package/src/components/AppsPreview/AppsPreview.module.css +128 -0
- package/src/components/Clients/Clients.jsx +205 -0
- package/src/components/Clients/Clients.module.css +166 -0
- package/src/components/ComposeBlock/ComposeBlock.jsx +70 -0
- package/src/components/ComposeBlock/ComposeBlock.module.css +74 -0
- package/src/components/ConductionBg/ConductionBg.jsx +150 -0
- package/src/components/ConductionBg/ConductionBg.module.css +41 -0
- package/src/components/ContentCard/ContentCard.jsx +126 -0
- package/src/components/ContentCard/ContentCard.module.css +84 -0
- package/src/components/ContentDetailHero/ContentDetailHero.jsx +136 -0
- package/src/components/ContentDetailHero/ContentDetailHero.module.css +96 -0
- package/src/components/ContentTypeFilter/ContentTypeFilter.jsx +103 -0
- package/src/components/ContentTypeFilter/ContentTypeFilter.module.css +60 -0
- package/src/components/ContentTypeFilter/contentTypes.js +58 -0
- package/src/components/CookieCli/CookieCli.jsx +223 -0
- package/src/components/CookieCli/CookieCli.module.css +166 -0
- package/src/components/CtaBanner/CtaBanner.jsx +61 -0
- package/src/components/CtaBanner/CtaBanner.module.css +65 -0
- package/src/components/DetailHero/DetailHero.jsx +143 -0
- package/src/components/DetailHero/DetailHero.module.css +154 -0
- package/src/components/Diagrams/Diagrams.jsx +148 -0
- package/src/components/EmployeeCard/EmployeeCard.jsx +127 -0
- package/src/components/EmployeeCard/EmployeeCard.module.css +144 -0
- package/src/components/ExternalAppShelf/ExternalAppShelf.jsx +61 -0
- package/src/components/ExternalAppShelf/ExternalAppShelf.module.css +90 -0
- package/src/components/FAQ/FAQ.jsx +42 -0
- package/src/components/FAQ/FAQ.module.css +74 -0
- package/src/components/FacetedFilters/FacetedFilters.jsx +125 -0
- package/src/components/FacetedFilters/FacetedFilters.module.css +133 -0
- package/src/components/FeatureGrid/FeatureGrid.jsx +94 -0
- package/src/components/FeatureGrid/FeatureGrid.module.css +114 -0
- package/src/components/FeatureList/FeatureList.jsx +54 -0
- package/src/components/FeatureList/FeatureList.module.css +52 -0
- package/src/components/FeaturedCard/FeaturedCard.jsx +101 -0
- package/src/components/FeaturedCard/FeaturedCard.module.css +98 -0
- package/src/components/GameModal/GameModal.jsx +197 -0
- package/src/components/GameModal/GameModal.module.css +184 -0
- package/src/components/Hero/Hero.jsx +101 -0
- package/src/components/Hero/Hero.module.css +95 -0
- package/src/components/HexBackground/HexBackground.jsx +56 -0
- package/src/components/HexBackground/HexBackground.module.css +73 -0
- package/src/components/HexNetwork/HexNetwork.jsx +141 -0
- package/src/components/HexNetwork/HexNetwork.module.css +187 -0
- package/src/components/HexRain/HexRain.jsx +81 -0
- package/src/components/HowSteps/HowSteps.jsx +57 -0
- package/src/components/HowSteps/HowSteps.module.css +52 -0
- package/src/components/ManagedCommonGround/ManagedCommonGround.jsx +78 -0
- package/src/components/ManagedCommonGround/ManagedCommonGround.module.css +16 -0
- package/src/components/NewsletterCta/NewsletterCta.jsx +83 -0
- package/src/components/NewsletterCta/NewsletterCta.module.css +103 -0
- package/src/components/PairCard/PairCard.jsx +58 -0
- package/src/components/PairCard/PairCard.module.css +54 -0
- package/src/components/PartnerCard/PartnerCard.jsx +130 -0
- package/src/components/PartnerCard/PartnerCard.module.css +198 -0
- package/src/components/PartnerDirectory/PartnerDirectory.jsx +122 -0
- package/src/components/PartnerDirectory/PartnerDirectory.module.css +25 -0
- package/src/components/PartnerSidecard/PartnerSidecard.jsx +116 -0
- package/src/components/PartnerSidecard/PartnerSidecard.module.css +185 -0
- package/src/components/Pipeline/Pipeline.jsx +198 -0
- package/src/components/Pipeline/Pipeline.module.css +206 -0
- package/src/components/PlatformDiagram/PlatformDiagram.jsx +110 -0
- package/src/components/PlatformOverview/PlatformOverview.jsx +68 -0
- package/src/components/PlatformOverview/PlatformOverview.module.css +71 -0
- package/src/components/ReferenceCard/ReferenceCard.jsx +44 -0
- package/src/components/ReferenceCard/ReferenceCard.module.css +57 -0
- package/src/components/RelatedPosts/RelatedPosts.jsx +58 -0
- package/src/components/RelatedPosts/RelatedPosts.module.css +51 -0
- package/src/components/RotatingCards/RotatingCards.jsx +98 -0
- package/src/components/RotatingCards/RotatingCards.module.css +153 -0
- package/src/components/Showcase/Showcase.jsx +129 -0
- package/src/components/Showcase/Showcase.module.css +168 -0
- package/src/components/SolutionCard/SolutionCard.jsx +83 -0
- package/src/components/SolutionCard/SolutionCard.module.css +99 -0
- package/src/components/StatsStrip/StatsStrip.jsx +38 -0
- package/src/components/StatsStrip/StatsStrip.module.css +53 -0
- package/src/components/WidgetShelf/WidgetShelf.jsx +67 -0
- package/src/components/WidgetShelf/WidgetShelf.module.css +73 -0
- package/src/components/index.js +96 -0
- package/src/components/primitives/AuthorByline.jsx +85 -0
- package/src/components/primitives/AuthorByline.module.css +57 -0
- package/src/components/primitives/BrandCitation.jsx +71 -0
- package/src/components/primitives/Button.jsx +46 -0
- package/src/components/primitives/Button.module.css +88 -0
- package/src/components/primitives/Card.jsx +42 -0
- package/src/components/primitives/Card.module.css +42 -0
- package/src/components/primitives/Eyebrow.jsx +37 -0
- package/src/components/primitives/Eyebrow.module.css +19 -0
- package/src/components/primitives/HexBullet.jsx +37 -0
- package/src/components/primitives/HexBullet.module.css +16 -0
- package/src/components/primitives/HexThumbnail.jsx +70 -0
- package/src/components/primitives/HexThumbnail.module.css +45 -0
- package/src/components/primitives/Pill.jsx +42 -0
- package/src/components/primitives/Pill.module.css +30 -0
- package/src/components/primitives/Section.jsx +51 -0
- package/src/components/primitives/Section.module.css +31 -0
- package/src/components/primitives/SectionHead.jsx +36 -0
- package/src/components/primitives/SectionHead.module.css +43 -0
- package/src/components/primitives/index.js +22 -0
- package/src/css/brand.css +158 -0
- package/src/css/tokens.css +12 -0
- package/src/data/app-downloads.js +42 -0
- package/src/diagrams/README.md +74 -0
- package/src/diagrams/cn-domain-tree.js +105 -0
- package/src/diagrams/cn-hex-prism.js +163 -0
- package/src/diagrams/cn-hex.js +181 -0
- package/src/diagrams/cn-honeycomb-bg.js +135 -0
- package/src/diagrams/cn-pipeline.js +150 -0
- package/src/diagrams/cn-platform.js +156 -0
- package/src/diagrams/cn-side-box.js +104 -0
- package/src/diagrams/index.js +28 -0
- package/src/index.js +183 -0
- package/src/theme/Footer/index.jsx +516 -0
- package/src/theme/MDXPage/index.jsx +134 -0
- package/src/theme/Navbar/index.jsx +120 -0
- package/src/theme/Navbar/styles.module.css +114 -0
- package/src/theme/brand.jsx +63 -0
- package/src/theme.js +45 -0
- package/src/utils/lazyScript.js +37 -0
- package/static/img/favicon.svg +14 -0
- package/static/img/honeycomb-scatter.svg +23 -0
- package/static/img/honeycomb-watermark.svg +108 -0
- package/static/img/logo-dark.svg +11 -0
- package/static/img/logo.svg +14 -0
- package/static/img/nextcloud-logo.svg +5 -0
- package/static/lib/canal-footer.css +418 -0
- package/static/lib/canal-footer.js +499 -0
- package/static/lib/clients-flow.js +317 -0
- package/static/lib/conduction-bg.css +50 -0
- package/static/lib/conduction-bg.js +122 -0
- package/static/lib/hex-rain.css +128 -0
- package/static/lib/hex-rain.js +284 -0
- package/static/lib/kade-cyclist.css +264 -0
- package/static/lib/kade-cyclist.js +420 -0
- package/static/lib/logo-memory.css +219 -0
- package/static/lib/logo-memory.js +540 -0
- package/static/lib/platform-diagram.css +458 -0
- package/static/lib/platform-diagram.js +414 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <HexRain />
|
|
3
|
+
*
|
|
4
|
+
* The "Twelve apps" mini-game from preview/pages/landing.html. A
|
|
5
|
+
* decorative continuous spawn loop drops orange hexes from the top of
|
|
6
|
+
* the column; first click on any hex starts a 60-second collect-the-
|
|
7
|
+
* apps game (12 unique apps to find, score = unique apps collected).
|
|
8
|
+
* Game-end fires a `connext:gameend` event that the gaming-modal
|
|
9
|
+
* component picks up to update its cookie and reveal the cross-site
|
|
10
|
+
* progress panel.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors preview/pages/landing.html .hex-rain. The actual game logic
|
|
13
|
+
* lives in sites/www/static/lib/hex-rain.js (266 lines of canvas-
|
|
14
|
+
* style imperative work that earns its place in plain JS, not JSX).
|
|
15
|
+
* This React wrapper:
|
|
16
|
+
* - Renders the mount point with the right class names so the
|
|
17
|
+
* runtime's selector matches
|
|
18
|
+
* - Lazy-loads the runtime CSS+JS via Head (idempotent across SPA
|
|
19
|
+
* route changes — the IIFE registers itself on first import,
|
|
20
|
+
* window.HexRain.hydrate() picks up new nodes)
|
|
21
|
+
* - Hooks into the runtime via useEffect so freshly-mounted nodes
|
|
22
|
+
* hydrate even when the SPA navigates between routes
|
|
23
|
+
*
|
|
24
|
+
* Usage in MDX:
|
|
25
|
+
*
|
|
26
|
+
* <Hero
|
|
27
|
+
* eyebrow="..."
|
|
28
|
+
* title="..."
|
|
29
|
+
* primaryCta={{...}}
|
|
30
|
+
* >
|
|
31
|
+
* <HexRain />
|
|
32
|
+
* </Hero>
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import React, {useEffect, useRef} from 'react';
|
|
36
|
+
import Head from '@docusaurus/Head';
|
|
37
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
38
|
+
import {useLazyScript} from '../../utils/lazyScript';
|
|
39
|
+
|
|
40
|
+
const ASSET_BASE = '/lib';
|
|
41
|
+
|
|
42
|
+
export default function HexRain({ariaLabel = 'Twelve apps mini-game, click each falling icon to collect the app', className}) {
|
|
43
|
+
const isBrowser = useIsBrowser();
|
|
44
|
+
const ref = useRef(null);
|
|
45
|
+
|
|
46
|
+
/* hex-rain.js is loaded post-hydration via this hook so the runtime's
|
|
47
|
+
DOM mutations don't trip React's hydration mismatch. See
|
|
48
|
+
utils/lazyScript.js. */
|
|
49
|
+
useLazyScript(ASSET_BASE + '/hex-rain.js', 'hex-rain');
|
|
50
|
+
|
|
51
|
+
/* The runtime exposes window.HexRain.hydrate() once it has registered
|
|
52
|
+
itself. Poll one rAF tick per frame until it's there, then call it
|
|
53
|
+
to attach the spawn loop to this mount point. */
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!isBrowser) return;
|
|
56
|
+
let cancelled = false;
|
|
57
|
+
function tryHydrate() {
|
|
58
|
+
if (cancelled) return;
|
|
59
|
+
if (window.HexRain?.hydrate) {
|
|
60
|
+
window.HexRain.hydrate();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
requestAnimationFrame(tryHydrate);
|
|
64
|
+
}
|
|
65
|
+
tryHydrate();
|
|
66
|
+
return () => { cancelled = true; };
|
|
67
|
+
}, [isBrowser]);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<Head>
|
|
72
|
+
<link rel="stylesheet" href={ASSET_BASE + '/hex-rain.css'} />
|
|
73
|
+
</Head>
|
|
74
|
+
<div
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={['hex-rain', className].filter(Boolean).join(' ')}
|
|
77
|
+
aria-label={ariaLabel}
|
|
78
|
+
/>
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <HowSteps /> + <HowStep />
|
|
3
|
+
*
|
|
4
|
+
* Numbered process-step row, surfaced from preview/pages/support.html
|
|
5
|
+
* (.how-row, .how-step). Three or more cards in a row with a tinted
|
|
6
|
+
* hex number indicator and a heading + body. Used on:
|
|
7
|
+
* - /support ("How to get help, in three steps")
|
|
8
|
+
* - /install ("How install works, three steps")
|
|
9
|
+
* - /partners/become ("Partner programme, three tiers")
|
|
10
|
+
*
|
|
11
|
+
* Each step's number-hex tints alternately by tier: cobalt, KNVB
|
|
12
|
+
* orange, cobalt-700. Pass `tier="1|2|3"` to override.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
*
|
|
16
|
+
* <HowSteps>
|
|
17
|
+
* <HowStep number="1" title="Read the docs first.">
|
|
18
|
+
* The /docs section covers most of what we'd answer in support.
|
|
19
|
+
* </HowStep>
|
|
20
|
+
* <HowStep number="2" title="Open a GitHub issue.">
|
|
21
|
+
* For bugs and feature requests. We triage daily.
|
|
22
|
+
* </HowStep>
|
|
23
|
+
* <HowStep number="3" title="Talk to a partner.">
|
|
24
|
+
* For implementation help, hosting, and rollout.
|
|
25
|
+
* </HowStep>
|
|
26
|
+
* </HowSteps>
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import React, {Children, cloneElement, isValidElement} from 'react';
|
|
30
|
+
import styles from './HowSteps.module.css';
|
|
31
|
+
|
|
32
|
+
export function HowStep({number, tier, title, children, className}) {
|
|
33
|
+
const tierKey = tier || (number ? String(((parseInt(number, 10) - 1) % 3) + 1) : '1');
|
|
34
|
+
const composed = [styles.step, styles['tier-' + tierKey], className].filter(Boolean).join(' ');
|
|
35
|
+
return (
|
|
36
|
+
<div className={composed}>
|
|
37
|
+
{number && <div className={styles.num}>{number}</div>}
|
|
38
|
+
{title && <h3 className={styles.title}>{title}</h3>}
|
|
39
|
+
{/* div not p: MDX may wrap inline children in its own <p>, which
|
|
40
|
+
would be invalid nested inside another <p>. */}
|
|
41
|
+
{children && <div className={styles.body}>{children}</div>}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default function HowSteps({columns = 3, children, className}) {
|
|
47
|
+
/* Auto-number children that don't pass an explicit `number`. Lets
|
|
48
|
+
callers write `<HowStep title="...">body</HowStep>` without
|
|
49
|
+
book-keeping. */
|
|
50
|
+
const numbered = Children.map(children, (child, i) => {
|
|
51
|
+
if (!isValidElement(child)) return child;
|
|
52
|
+
if (child.props.number != null) return child;
|
|
53
|
+
return cloneElement(child, {number: i + 1});
|
|
54
|
+
});
|
|
55
|
+
const composed = [styles.row, styles['cols-' + columns], className].filter(Boolean).join(' ');
|
|
56
|
+
return <div className={composed}>{numbered}</div>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <HowSteps /> styles. Mirrors .how-row + .how-step from support.html.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.row { display: grid; gap: 24px; font-family: var(--conduction-typography-font-family-body); }
|
|
6
|
+
.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
|
7
|
+
.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
|
8
|
+
.cols-4 { grid-template-columns: repeat(4, 1fr); }
|
|
9
|
+
@media (max-width: 900px) { .row { grid-template-columns: 1fr; } }
|
|
10
|
+
|
|
11
|
+
.step {
|
|
12
|
+
background: white;
|
|
13
|
+
border: 1px solid var(--c-cobalt-100);
|
|
14
|
+
border-radius: var(--radius-lg);
|
|
15
|
+
padding: 32px;
|
|
16
|
+
position: relative;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.num {
|
|
20
|
+
width: 48px;
|
|
21
|
+
height: 56px;
|
|
22
|
+
clip-path: var(--hex-pointy-top);
|
|
23
|
+
background: var(--c-blue-cobalt);
|
|
24
|
+
color: white;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
font-weight: 700;
|
|
29
|
+
font-size: 18px;
|
|
30
|
+
margin-bottom: 18px;
|
|
31
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.tier-1 .num { background: var(--c-blue-cobalt); }
|
|
35
|
+
.tier-2 .num { background: var(--c-orange-knvb); }
|
|
36
|
+
.tier-3 .num { background: var(--c-cobalt-700); }
|
|
37
|
+
|
|
38
|
+
.title {
|
|
39
|
+
font-size: 20px;
|
|
40
|
+
font-weight: 700;
|
|
41
|
+
letter-spacing: -0.01em;
|
|
42
|
+
margin: 0 0 10px;
|
|
43
|
+
color: var(--c-cobalt-900);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.body {
|
|
47
|
+
font-size: 14px;
|
|
48
|
+
color: var(--c-cobalt-700);
|
|
49
|
+
line-height: 1.6;
|
|
50
|
+
margin: 0;
|
|
51
|
+
}
|
|
52
|
+
.body :global(.next-blue) { color: var(--c-nextcloud-blue); }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <ManagedCommonGround />
|
|
3
|
+
*
|
|
4
|
+
* Section that pitches the managed Common Ground+ tenant: a SectionHead,
|
|
5
|
+
* a centred CTA pair, a PairRow of the components on offer, and a
|
|
6
|
+
* compliance footnote. Same shape on /support and /commonground;
|
|
7
|
+
* keeping it as a shared component prevents copy drift between the
|
|
8
|
+
* two surfaces (price changes, new components, eligibility tweaks
|
|
9
|
+
* land once and propagate).
|
|
10
|
+
*
|
|
11
|
+
* Content is passed as props so callers can localise per page (NL +
|
|
12
|
+
* EN MDX twins). The component owns layout and brand-look only.
|
|
13
|
+
*
|
|
14
|
+
* Usage in MDX:
|
|
15
|
+
*
|
|
16
|
+
* <ManagedCommonGround
|
|
17
|
+
* eyebrow="Managed Common Ground"
|
|
18
|
+
* title={<>A government-only Nextcloud framework.<br/>€250 per component per month.</>}
|
|
19
|
+
* lede={<>For public-sector clients we run a managed <NextBlue>Nextcloud</NextBlue> tenant...</>}
|
|
20
|
+
* primaryCta={{label: 'Talk to us', href: 'mailto:info@conduction.nl'}}
|
|
21
|
+
* secondaryCta={{label: 'Compliance signals', href: '/iso'}}
|
|
22
|
+
* components={[
|
|
23
|
+
* {name: 'OpenRegister', href: '/apps/openregister', why: 'Typed register store...', icon: <svg>...</svg>},
|
|
24
|
+
* ...
|
|
25
|
+
* ]}
|
|
26
|
+
* footnote="Available to public-sector clients only..."
|
|
27
|
+
* />
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import React from 'react';
|
|
31
|
+
import Section from '../primitives/Section';
|
|
32
|
+
import SectionHead from '../primitives/SectionHead';
|
|
33
|
+
import Button from '../primitives/Button';
|
|
34
|
+
import PairCard, {PairRow} from '../PairCard/PairCard';
|
|
35
|
+
import styles from './ManagedCommonGround.module.css';
|
|
36
|
+
|
|
37
|
+
export default function ManagedCommonGround({
|
|
38
|
+
eyebrow,
|
|
39
|
+
title,
|
|
40
|
+
lede,
|
|
41
|
+
primaryCta,
|
|
42
|
+
secondaryCta,
|
|
43
|
+
components = [],
|
|
44
|
+
footnote,
|
|
45
|
+
background,
|
|
46
|
+
spacing = 'default',
|
|
47
|
+
}) {
|
|
48
|
+
return (
|
|
49
|
+
<Section spacing={spacing} background={background}>
|
|
50
|
+
<SectionHead eyebrow={eyebrow} title={title} lede={lede} align="stack" />
|
|
51
|
+
|
|
52
|
+
{(primaryCta || secondaryCta) && (
|
|
53
|
+
<div className={styles.actions}>
|
|
54
|
+
{primaryCta && (
|
|
55
|
+
<Button variant="primary" size="lg" href={primaryCta.href}>
|
|
56
|
+
{primaryCta.label}
|
|
57
|
+
</Button>
|
|
58
|
+
)}
|
|
59
|
+
{secondaryCta && (
|
|
60
|
+
<Button variant="secondary" size="lg" href={secondaryCta.href}>
|
|
61
|
+
{secondaryCta.label}
|
|
62
|
+
</Button>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{components.length > 0 && (
|
|
68
|
+
<PairRow columns={3}>
|
|
69
|
+
{components.map((c, i) => (
|
|
70
|
+
<PairCard key={i} href={c.href} icon={c.icon} name={c.name} why={c.why} />
|
|
71
|
+
))}
|
|
72
|
+
</PairRow>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{footnote && <p className={styles.footnote}>{footnote}</p>}
|
|
76
|
+
</Section>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.actions {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 14px;
|
|
4
|
+
flex-wrap: wrap;
|
|
5
|
+
margin-bottom: var(--space-8);
|
|
6
|
+
justify-content: center;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.footnote {
|
|
10
|
+
font-size: 13px;
|
|
11
|
+
color: var(--c-cobalt-700);
|
|
12
|
+
text-align: center;
|
|
13
|
+
margin: var(--space-5) auto 0;
|
|
14
|
+
max-width: 64ch;
|
|
15
|
+
line-height: 1.55;
|
|
16
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <NewsletterCta />
|
|
3
|
+
*
|
|
4
|
+
* Cobalt-50 panel with title, lede, an email input, a submit button,
|
|
5
|
+
* and a fineprint line. Used at the bottom of the academy landing
|
|
6
|
+
* and on detail pages.
|
|
7
|
+
*
|
|
8
|
+
* The form is unwired by default (`onSubmit` no-op, `action` left
|
|
9
|
+
* empty). Pass an `action` URL or an `onSubmit` handler to wire it.
|
|
10
|
+
* Pass `inverse` to render on a cobalt-900 ground for use inside dark
|
|
11
|
+
* sections.
|
|
12
|
+
*
|
|
13
|
+
* Usage in MDX:
|
|
14
|
+
*
|
|
15
|
+
* <NewsletterCta
|
|
16
|
+
* title="New posts in your inbox, monthly."
|
|
17
|
+
* lede="One mail a month. New guides, case studies, webinars."
|
|
18
|
+
* placeholder="je@bedrijf.nl"
|
|
19
|
+
* submitLabel="Subscribe"
|
|
20
|
+
* fineprint="We mail vanuit info@conduction.nl. Geen lijstverkoop."
|
|
21
|
+
* />
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import React from 'react';
|
|
25
|
+
import styles from './NewsletterCta.module.css';
|
|
26
|
+
|
|
27
|
+
export default function NewsletterCta({
|
|
28
|
+
title,
|
|
29
|
+
lede,
|
|
30
|
+
placeholder = 'jij@bedrijf.nl',
|
|
31
|
+
submitLabel = 'Aanmelden',
|
|
32
|
+
fineprint,
|
|
33
|
+
action,
|
|
34
|
+
method = 'POST',
|
|
35
|
+
name = 'email',
|
|
36
|
+
inverse = false,
|
|
37
|
+
onSubmit,
|
|
38
|
+
className,
|
|
39
|
+
}) {
|
|
40
|
+
const composed = [
|
|
41
|
+
styles.cta,
|
|
42
|
+
inverse ? styles.inverse : null,
|
|
43
|
+
className,
|
|
44
|
+
].filter(Boolean).join(' ');
|
|
45
|
+
|
|
46
|
+
const handleSubmit = (event) => {
|
|
47
|
+
if (onSubmit) {
|
|
48
|
+
onSubmit(event);
|
|
49
|
+
} else if (!action) {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<section className={composed}>
|
|
56
|
+
<div className={styles.copy}>
|
|
57
|
+
{title && <h3 className={styles.title}>{title}</h3>}
|
|
58
|
+
{lede && <p className={styles.lede}>{lede}</p>}
|
|
59
|
+
</div>
|
|
60
|
+
<div className={styles.form}>
|
|
61
|
+
<form
|
|
62
|
+
className={styles.formRow}
|
|
63
|
+
action={action || undefined}
|
|
64
|
+
method={method}
|
|
65
|
+
onSubmit={handleSubmit}
|
|
66
|
+
>
|
|
67
|
+
<input
|
|
68
|
+
type="email"
|
|
69
|
+
name={name}
|
|
70
|
+
className={styles.input}
|
|
71
|
+
placeholder={placeholder}
|
|
72
|
+
aria-label={placeholder}
|
|
73
|
+
required
|
|
74
|
+
/>
|
|
75
|
+
<button type="submit" className={styles.submit}>
|
|
76
|
+
{submitLabel}
|
|
77
|
+
</button>
|
|
78
|
+
</form>
|
|
79
|
+
{fineprint && <p className={styles.fineprint}>{fineprint}</p>}
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <NewsletterCta /> styles. Mirrors .newsletter-cta in
|
|
3
|
+
* preview/components/academy.css.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.cta {
|
|
7
|
+
display: grid;
|
|
8
|
+
grid-template-columns: 1.1fr 1fr;
|
|
9
|
+
gap: var(--space-12);
|
|
10
|
+
padding: var(--space-12);
|
|
11
|
+
background: var(--c-cobalt-50);
|
|
12
|
+
border-radius: var(--radius-xl);
|
|
13
|
+
align-items: center;
|
|
14
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
15
|
+
}
|
|
16
|
+
@media (max-width: 900px) {
|
|
17
|
+
.cta { grid-template-columns: 1fr; padding: var(--space-8); }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.copy { min-width: 0; }
|
|
21
|
+
|
|
22
|
+
.title {
|
|
23
|
+
font-size: 28px;
|
|
24
|
+
font-weight: 700;
|
|
25
|
+
letter-spacing: -0.02em;
|
|
26
|
+
color: var(--c-cobalt-900);
|
|
27
|
+
margin: 0 0 var(--space-3);
|
|
28
|
+
line-height: 1.2;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.lede {
|
|
32
|
+
font-size: 16px;
|
|
33
|
+
color: var(--c-cobalt-700);
|
|
34
|
+
margin: 0;
|
|
35
|
+
line-height: 1.55;
|
|
36
|
+
max-width: 42ch;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.form { min-width: 0; }
|
|
40
|
+
|
|
41
|
+
.formRow {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 8px;
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.input {
|
|
48
|
+
flex: 1;
|
|
49
|
+
padding: 12px 16px;
|
|
50
|
+
border: 1px solid var(--c-cobalt-100);
|
|
51
|
+
border-radius: var(--radius-md);
|
|
52
|
+
background: white;
|
|
53
|
+
color: var(--c-cobalt-900);
|
|
54
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
55
|
+
font-size: 14px;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
}
|
|
58
|
+
.input::placeholder { color: var(--c-cobalt-400); }
|
|
59
|
+
.input:focus {
|
|
60
|
+
outline: none;
|
|
61
|
+
border-color: var(--c-blue-cobalt);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.submit {
|
|
65
|
+
padding: 12px 22px;
|
|
66
|
+
border: 0;
|
|
67
|
+
border-radius: var(--radius-md);
|
|
68
|
+
background: var(--c-blue-cobalt);
|
|
69
|
+
color: white;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
transition: background 120ms ease;
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
}
|
|
77
|
+
.submit:hover { background: var(--c-cobalt-700); }
|
|
78
|
+
|
|
79
|
+
.fineprint {
|
|
80
|
+
margin: var(--space-3) 0 0;
|
|
81
|
+
font-size: 12px;
|
|
82
|
+
color: var(--c-cobalt-400);
|
|
83
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
84
|
+
letter-spacing: 0.04em;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Inverse variant for placement inside dark sections. */
|
|
88
|
+
.inverse {
|
|
89
|
+
background: var(--c-cobalt-900);
|
|
90
|
+
color: white;
|
|
91
|
+
}
|
|
92
|
+
.inverse .title { color: white; }
|
|
93
|
+
.inverse .lede { color: var(--c-cobalt-200); }
|
|
94
|
+
.inverse .input {
|
|
95
|
+
background: var(--c-cobalt-800);
|
|
96
|
+
border-color: var(--c-cobalt-700);
|
|
97
|
+
color: white;
|
|
98
|
+
}
|
|
99
|
+
.inverse .input::placeholder { color: var(--c-cobalt-300); }
|
|
100
|
+
.inverse .input:focus { border-color: white; }
|
|
101
|
+
.inverse .submit { background: white; color: var(--c-cobalt-900); }
|
|
102
|
+
.inverse .submit:hover { background: var(--c-cobalt-50); }
|
|
103
|
+
.inverse .fineprint { color: var(--c-cobalt-300); }
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <PairCard /> + <PairRow />
|
|
3
|
+
*
|
|
4
|
+
* Compact "related item" card from preview/components/pair-cards.html.
|
|
5
|
+
* Used as the "Pairs well with" / "See also" row on app and solution
|
|
6
|
+
* detail pages. Smaller and denser than <AppCard/>.
|
|
7
|
+
*
|
|
8
|
+
* Visual: cobalt icon-hex on the left, name + why on the right.
|
|
9
|
+
*
|
|
10
|
+
* Brand icons (Nextcloud, Common Ground+) ship with their own hex
|
|
11
|
+
* fill and ink colour per the design-system rule. Pass iconBg +
|
|
12
|
+
* iconColor to override the default cobalt + white pair, and the
|
|
13
|
+
* card switches to the "brand" icon-wrap variant: the SVG renders
|
|
14
|
+
* as a filled mark instead of a stroke-only glyph.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
*
|
|
18
|
+
* <PairRow>
|
|
19
|
+
* <PairCard
|
|
20
|
+
* href="/apps/opencatalogi"
|
|
21
|
+
* icon={<svg>...</svg>}
|
|
22
|
+
* name="OpenCatalogi"
|
|
23
|
+
* why="Surfaces every register as a public, searchable catalog entry."
|
|
24
|
+
* />
|
|
25
|
+
* <PairCard
|
|
26
|
+
* href="/connext"
|
|
27
|
+
* icon={<svg>{nextcloud logo}</svg>}
|
|
28
|
+
* iconBg="var(--c-nextcloud-blue)"
|
|
29
|
+
* iconColor="white"
|
|
30
|
+
* name={<>Con<NextBlue>Next</NextBlue></>}
|
|
31
|
+
* why="..."
|
|
32
|
+
* />
|
|
33
|
+
* </PairRow>
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import React from 'react';
|
|
37
|
+
import styles from './PairCard.module.css';
|
|
38
|
+
|
|
39
|
+
export function PairRow({columns = 3, children, className}) {
|
|
40
|
+
const composed = [styles.row, styles['cols-' + columns], className].filter(Boolean).join(' ');
|
|
41
|
+
return <div className={composed}>{children}</div>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default function PairCard({href, icon, iconBg, iconColor, name, why, className}) {
|
|
45
|
+
const Tag = href ? 'a' : 'div';
|
|
46
|
+
const isBrand = Boolean(iconBg || iconColor);
|
|
47
|
+
const wrapClass = [styles.iconWrap, isBrand && styles.iconWrapBrand].filter(Boolean).join(' ');
|
|
48
|
+
const wrapStyle = isBrand ? {background: iconBg, color: iconColor} : undefined;
|
|
49
|
+
return (
|
|
50
|
+
<Tag href={href} className={[styles.card, className].filter(Boolean).join(' ')}>
|
|
51
|
+
<div className={wrapClass} style={wrapStyle}>{icon}</div>
|
|
52
|
+
<div>
|
|
53
|
+
{name && <div className={styles.name}>{name}</div>}
|
|
54
|
+
{why && <div className={styles.why}>{why}</div>}
|
|
55
|
+
</div>
|
|
56
|
+
</Tag>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <PairCard /> styles. Mirrors pair-cards.css exactly.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.row { display: grid; gap: 16px; }
|
|
6
|
+
.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
|
7
|
+
.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
|
8
|
+
@media (max-width: 900px) { .row { grid-template-columns: 1fr; } }
|
|
9
|
+
|
|
10
|
+
.card {
|
|
11
|
+
background: white;
|
|
12
|
+
border: 1px solid var(--c-cobalt-100);
|
|
13
|
+
border-radius: var(--radius-lg);
|
|
14
|
+
padding: 20px;
|
|
15
|
+
text-decoration: none;
|
|
16
|
+
color: inherit;
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: 14px;
|
|
19
|
+
align-items: flex-start;
|
|
20
|
+
transition: all 160ms ease;
|
|
21
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
22
|
+
}
|
|
23
|
+
.card:hover {
|
|
24
|
+
border-color: var(--c-blue-cobalt);
|
|
25
|
+
transform: translateY(-1px);
|
|
26
|
+
text-decoration: none;
|
|
27
|
+
color: inherit;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.iconWrap {
|
|
31
|
+
width: 44px;
|
|
32
|
+
height: 50px;
|
|
33
|
+
clip-path: var(--hex-pointy-top);
|
|
34
|
+
background: var(--c-blue-cobalt);
|
|
35
|
+
color: white;
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
flex-shrink: 0;
|
|
40
|
+
}
|
|
41
|
+
.iconWrap svg { width: 20px; height: 20px; stroke: currentColor; stroke-width: 2; fill: none; }
|
|
42
|
+
|
|
43
|
+
/* Brand-icon variant: the SVG is a filled brand mark (Nextcloud logo,
|
|
44
|
+
Common Ground logo) rather than a stroke-only glyph. Override the
|
|
45
|
+
stroke-only rule so the logo renders solid in its currentColor. */
|
|
46
|
+
.iconWrapBrand svg {
|
|
47
|
+
width: auto;
|
|
48
|
+
height: 22px;
|
|
49
|
+
fill: currentColor;
|
|
50
|
+
stroke: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.name { font-size: 15px; font-weight: 600; margin-bottom: 4px; color: var(--c-cobalt-900); }
|
|
54
|
+
.why { font-size: 13px; color: var(--c-cobalt-700); line-height: 1.5; }
|