@conduction/docusaurus-preset 1.7.0 → 2.0.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/package.json +1 -1
- package/src/components/PartnerCard/PartnerCard.jsx +11 -8
- package/src/components/PartnerCard/PartnerCard.module.css +17 -10
- package/src/components/PartnerDirectory/PartnerDirectory.jsx +56 -21
- package/src/components/PartnerSidecard/PartnerSidecard.jsx +13 -13
- package/src/components/PartnerSidecard/PartnerSidecard.module.css +9 -7
package/package.json
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Partner card from preview/components/partner-cards.html.
|
|
5
5
|
*
|
|
6
|
-
* Three tiers:
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
6
|
+
* Three tiers (low → high):
|
|
7
|
+
* - host (default, white panel + cobalt-50 tier pill) —
|
|
8
|
+
* ships our apps, no SLA with Conduction.
|
|
9
|
+
* - service (white panel + gold tier pill) — SLA, third-line
|
|
10
|
+
* support, roadmap input.
|
|
11
|
+
* - certified (cobalt-fill inverse + white tier pill) — trained,
|
|
12
|
+
* joint roadmap, tender-eligible.
|
|
10
13
|
*
|
|
11
14
|
* Visual structure:
|
|
12
15
|
* ┌─────────────────────────────┐
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
* <PartnerGrid>
|
|
26
29
|
* <PartnerCard
|
|
27
30
|
* href="/partners/yard"
|
|
28
|
-
* tier="
|
|
31
|
+
* tier="host"
|
|
29
32
|
* name="YARD"
|
|
30
33
|
* logo="/img/partners/yard.png"
|
|
31
34
|
* summary={<>Digital design- en development-bureau uit Utrecht...</>}
|
|
@@ -39,7 +42,7 @@ import React from 'react';
|
|
|
39
42
|
import HexBullet from '../primitives/HexBullet';
|
|
40
43
|
import styles from './PartnerCard.module.css';
|
|
41
44
|
|
|
42
|
-
const TIER_LABELS = {
|
|
45
|
+
const TIER_LABELS = {host: 'Host', service: 'Service', certified: 'Certified'};
|
|
43
46
|
|
|
44
47
|
export function PartnerGrid({columns = 3, children, className}) {
|
|
45
48
|
const composed = [styles.grid, styles['cols-' + columns], className].filter(Boolean).join(' ');
|
|
@@ -49,7 +52,7 @@ export function PartnerGrid({columns = 3, children, className}) {
|
|
|
49
52
|
export default function PartnerCard({
|
|
50
53
|
variant = 'full',
|
|
51
54
|
href,
|
|
52
|
-
tier = '
|
|
55
|
+
tier = 'host',
|
|
53
56
|
name,
|
|
54
57
|
logo,
|
|
55
58
|
logoAlt,
|
|
@@ -92,7 +95,7 @@ export default function PartnerCard({
|
|
|
92
95
|
className,
|
|
93
96
|
].filter(Boolean).join(' ');
|
|
94
97
|
const Tag = href ? 'a' : 'div';
|
|
95
|
-
const bulletColor = tier === '
|
|
98
|
+
const bulletColor = tier === 'certified' ? 'var(--c-orange-knvb)' : 'var(--c-blue-cobalt)';
|
|
96
99
|
|
|
97
100
|
return (
|
|
98
101
|
<Tag href={href} className={composed}>
|
|
@@ -49,9 +49,16 @@
|
|
|
49
49
|
color: var(--c-cobalt-700);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
.tier-
|
|
54
|
-
|
|
52
|
+
/* Service tier: gold pill on a white card. */
|
|
53
|
+
.tier-service .tier { background: var(--c-gold-500); color: var(--c-cobalt-900); }
|
|
54
|
+
|
|
55
|
+
/* Certified tier: cobalt-fill inverse card. The compound .card.tier-certified
|
|
56
|
+
selector is deliberate — it lifts specificity above the plain .card rule
|
|
57
|
+
so the cobalt fill reliably wins over .card { background: white } even
|
|
58
|
+
after CSS-module bundlers reshuffle declaration order. Same for the
|
|
59
|
+
nested colour rules below. */
|
|
60
|
+
.card.tier-certified { background: var(--c-blue-cobalt); color: white; border-color: var(--c-blue-cobalt); }
|
|
61
|
+
.tier-certified .tier { background: white; color: var(--c-blue-cobalt); }
|
|
55
62
|
|
|
56
63
|
.avatar {
|
|
57
64
|
display: inline-flex;
|
|
@@ -73,12 +80,12 @@
|
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
.name { font-size: 18px; font-weight: 700; color: var(--c-cobalt-900); }
|
|
76
|
-
.tier-
|
|
83
|
+
.tier-certified .name { color: white; }
|
|
77
84
|
|
|
78
85
|
.summary { font-size: 14px; line-height: 1.55; color: var(--c-cobalt-700); }
|
|
79
86
|
.summary :global(.next-blue) { color: var(--c-nextcloud-blue); }
|
|
80
|
-
.tier-
|
|
81
|
-
.tier-
|
|
87
|
+
.tier-certified .summary { color: rgba(255, 255, 255, 0.78); }
|
|
88
|
+
.tier-certified .summary :global(.next-blue) { color: var(--c-nextcloud-cyan); }
|
|
82
89
|
|
|
83
90
|
.apps {
|
|
84
91
|
margin-top: auto;
|
|
@@ -88,7 +95,7 @@
|
|
|
88
95
|
gap: 6px;
|
|
89
96
|
flex-wrap: wrap;
|
|
90
97
|
}
|
|
91
|
-
.tier-
|
|
98
|
+
.tier-certified .apps { border-top-color: rgba(255, 255, 255, 0.18); }
|
|
92
99
|
|
|
93
100
|
.appPill {
|
|
94
101
|
display: inline-flex;
|
|
@@ -101,7 +108,7 @@
|
|
|
101
108
|
font-size: 12px;
|
|
102
109
|
font-family: var(--conduction-typography-font-family-code);
|
|
103
110
|
}
|
|
104
|
-
.tier-
|
|
111
|
+
.tier-certified .appPill { background: rgba(255, 255, 255, 0.12); color: white; }
|
|
105
112
|
|
|
106
113
|
/* Become-a-partner CTA tile */
|
|
107
114
|
.become {
|
|
@@ -182,8 +189,8 @@
|
|
|
182
189
|
max-height: 28px;
|
|
183
190
|
object-fit: contain;
|
|
184
191
|
}
|
|
185
|
-
.other.tier-
|
|
186
|
-
.other.tier-
|
|
192
|
+
.other.tier-service .miniAvatar { box-shadow: inset 0 0 0 2px var(--c-gold-500); }
|
|
193
|
+
.other.tier-certified .miniAvatar { box-shadow: inset 0 0 0 2px var(--c-orange-knvb); }
|
|
187
194
|
|
|
188
195
|
.otherName {
|
|
189
196
|
font-size: 15px;
|
|
@@ -5,27 +5,32 @@
|
|
|
5
5
|
* <PartnerGrid/>, <PartnerCard/>, and an optional <BecomePartner/> CTA.
|
|
6
6
|
*
|
|
7
7
|
* Facets are auto-derived from the partners array:
|
|
8
|
-
* - tier:
|
|
9
|
-
* -
|
|
8
|
+
* - tier: Certified / Service / Host (only shown when ≥1 partner)
|
|
9
|
+
* - offering: union of every partners[].apps[] AND .solutions[]
|
|
10
|
+
* value, sorted by count. Solutions get pretty labels;
|
|
11
|
+
* unknown slugs fall back to the slug itself.
|
|
10
12
|
*
|
|
11
13
|
* Filter logic: a partner is shown when it matches every active facet.
|
|
12
14
|
* Within a facet, multiple selections are OR-ed (any-of). Across
|
|
13
|
-
* facets, selections are AND-ed (all-of).
|
|
15
|
+
* facets, selections are AND-ed (all-of). The offering facet matches
|
|
16
|
+
* against the union of the partner's apps and solutions, so picking
|
|
17
|
+
* "Woo" shows every partner that ships the Woo solution.
|
|
14
18
|
*
|
|
15
19
|
* Usage in MDX:
|
|
16
20
|
*
|
|
17
21
|
* <PartnerDirectory
|
|
18
22
|
* partners={[
|
|
19
|
-
* {href: '/partners/acato', tier: '
|
|
23
|
+
* {href: '/partners/acato', tier: 'certified', name: 'Acato',
|
|
20
24
|
* logo: '/img/partners/acato.png',
|
|
21
|
-
* summary: <>
|
|
22
|
-
* apps: ['
|
|
25
|
+
* summary: <>Digital agency uit Almere…</>,
|
|
26
|
+
* apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'],
|
|
27
|
+
* solutions: ['woo']},
|
|
23
28
|
* …
|
|
24
29
|
* ]}
|
|
25
30
|
* becomePartner={{
|
|
26
31
|
* href: '#become-a-partner',
|
|
27
32
|
* title: 'Ship Conduction to your customers.',
|
|
28
|
-
* body: '
|
|
33
|
+
* body: 'Three tiers: Host, Service, Certified.',
|
|
29
34
|
* ctaLabel: 'Apply below',
|
|
30
35
|
* }}
|
|
31
36
|
* />
|
|
@@ -36,8 +41,21 @@ import FacetedFilters from '../FacetedFilters/FacetedFilters';
|
|
|
36
41
|
import PartnerCard, {PartnerGrid, BecomePartner} from '../PartnerCard/PartnerCard';
|
|
37
42
|
import styles from './PartnerDirectory.module.css';
|
|
38
43
|
|
|
39
|
-
const TIER_LABELS = {
|
|
40
|
-
const TIER_ORDER = ['
|
|
44
|
+
const TIER_LABELS = {host: 'Host', service: 'Service', certified: 'Certified'};
|
|
45
|
+
const TIER_ORDER = ['certified', 'service', 'host'];
|
|
46
|
+
|
|
47
|
+
// Pretty labels for solution slugs that the catalog uses. Anything not
|
|
48
|
+
// in this map falls back to the raw slug, so a new solution keeps
|
|
49
|
+
// working without an explicit registration.
|
|
50
|
+
const SOLUTION_LABELS = {
|
|
51
|
+
woo: 'Woo',
|
|
52
|
+
archief: 'Archief',
|
|
53
|
+
'software-catalog': 'Software catalog',
|
|
54
|
+
'mkb-workspace': 'MKB workspace',
|
|
55
|
+
zaakafhandeling: 'Zaakafhandeling',
|
|
56
|
+
'legacy-erp': 'Legacy ERP',
|
|
57
|
+
'nen-7510': 'NEN 7510',
|
|
58
|
+
};
|
|
41
59
|
|
|
42
60
|
function deriveFacets(partners) {
|
|
43
61
|
const tierItems = TIER_ORDER
|
|
@@ -48,19 +66,29 @@ function deriveFacets(partners) {
|
|
|
48
66
|
}))
|
|
49
67
|
.filter(item => item.count > 0);
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
// Combined offering facet: apps + solutions. Each entry records its
|
|
70
|
+
// kind so the filter can match against the right partner field.
|
|
71
|
+
// Items are stored under a stable value namespaced by kind
|
|
72
|
+
// ('app:OpenCatalogi', 'solution:woo') so an app and a solution
|
|
73
|
+
// with the same name never collide.
|
|
74
|
+
const offeringCounts = new Map(); // value → {label, count, kind, raw}
|
|
75
|
+
const bump = (kind, raw, label) => {
|
|
76
|
+
const value = `${kind}:${raw}`;
|
|
77
|
+
const existing = offeringCounts.get(value);
|
|
78
|
+
if (existing) existing.count += 1;
|
|
79
|
+
else offeringCounts.set(value, {value, label, count: 1, kind, raw});
|
|
80
|
+
};
|
|
52
81
|
for (const p of partners) {
|
|
53
|
-
for (const a of p.apps || [])
|
|
54
|
-
|
|
55
|
-
}
|
|
82
|
+
for (const a of p.apps || []) bump('app', a, a);
|
|
83
|
+
for (const s of p.solutions || []) bump('solution', s, SOLUTION_LABELS[s] || s);
|
|
56
84
|
}
|
|
57
|
-
const
|
|
58
|
-
.sort((a, b) => b
|
|
59
|
-
.map((
|
|
85
|
+
const offeringItems = Array.from(offeringCounts.values())
|
|
86
|
+
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label))
|
|
87
|
+
.map(({value, label, count}) => ({value, label, count}));
|
|
60
88
|
|
|
61
89
|
const facets = [];
|
|
62
|
-
if (tierItems.length
|
|
63
|
-
if (
|
|
90
|
+
if (tierItems.length > 0) facets.push({key: 'tier', label: 'Partner tier', items: tierItems});
|
|
91
|
+
if (offeringItems.length > 0) facets.push({key: 'offering', label: 'Apps and solutions', items: offeringItems});
|
|
64
92
|
return facets;
|
|
65
93
|
}
|
|
66
94
|
|
|
@@ -68,10 +96,17 @@ function partnerMatches(partner, selected) {
|
|
|
68
96
|
const tierFilter = selected.tier || [];
|
|
69
97
|
if (tierFilter.length > 0 && !tierFilter.includes(partner.tier)) return false;
|
|
70
98
|
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
99
|
+
const offeringFilter = selected.offering || [];
|
|
100
|
+
if (offeringFilter.length > 0) {
|
|
73
101
|
const partnerApps = partner.apps || [];
|
|
74
|
-
|
|
102
|
+
const partnerSolutions = partner.solutions || [];
|
|
103
|
+
const ok = offeringFilter.some(value => {
|
|
104
|
+
const [kind, raw] = value.split(':');
|
|
105
|
+
if (kind === 'app') return partnerApps.includes(raw);
|
|
106
|
+
if (kind === 'solution') return partnerSolutions.includes(raw);
|
|
107
|
+
return false;
|
|
108
|
+
});
|
|
109
|
+
if (!ok) return false;
|
|
75
110
|
}
|
|
76
111
|
|
|
77
112
|
return true;
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Narrow info panel for the partner detail page right column. Three
|
|
5
5
|
* stacked sections:
|
|
6
6
|
* 1. Tier badge. Pulls the same colour treatment as <PartnerCard>
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
9
|
-
* Conduction
|
|
10
|
-
*
|
|
7
|
+
* (Host = cobalt-50 pill, Service = gold pill,
|
|
8
|
+
* Certified = white pill on a cobalt-fill card,
|
|
9
|
+
* with a small gold Conduction avatar to read as
|
|
10
|
+
* a Conduction-issued credential).
|
|
11
11
|
* 2. Apps supported. Chip row from partner.apps.
|
|
12
12
|
* 3. Solutions shipped. Chip row from solutionsBySlugs(partner.solutions),
|
|
13
13
|
* each linking to its /solutions/<slug> page.
|
|
@@ -40,18 +40,18 @@ import React from 'react';
|
|
|
40
40
|
import styles from './PartnerSidecard.module.css';
|
|
41
41
|
|
|
42
42
|
const TIER_LABELS = {
|
|
43
|
-
|
|
43
|
+
host: 'Host',
|
|
44
|
+
service: 'Service',
|
|
44
45
|
certified: 'Certified',
|
|
45
|
-
strategic: 'Strategic',
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const TIER_BLURB = {
|
|
49
|
-
|
|
50
|
-
'
|
|
49
|
+
host:
|
|
50
|
+
'Host partners ship our open-source apps to their customers. They sell, host, and resell Nextcloud-supported variants through their own channels. No SLA with Conduction, no direct line to our support, not eligible to respond to tenders with our products.',
|
|
51
|
+
service:
|
|
52
|
+
'Service partners hold an SLA with Conduction. Their team can call us directly for third-line support, with named contacts and input on bug priority and feature requests. They run rollouts; we back them up.',
|
|
51
53
|
certified:
|
|
52
|
-
'Certified
|
|
53
|
-
strategic:
|
|
54
|
-
'Strategic partner with shared roadmap commitments and joint engineering. By invitation. Co-sale on government tenders, co-branded hosting offerings.',
|
|
54
|
+
'Certified partners are trained, audited, and on the joint roadmap with Conduction. Eligible to co-sale on public tenders with our products, and the Conduction-credential mark on their own collateral.',
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
export default function PartnerSidecard({
|
|
@@ -61,7 +61,7 @@ export default function PartnerSidecard({
|
|
|
61
61
|
className,
|
|
62
62
|
}) {
|
|
63
63
|
if (!partner) return null;
|
|
64
|
-
const tier = partner.tier || '
|
|
64
|
+
const tier = partner.tier || 'host';
|
|
65
65
|
const tierLabel = TIER_LABELS[tier] || tier;
|
|
66
66
|
const composed = [styles.rail, styles[`tier-${tier}`], className].filter(Boolean).join(' ');
|
|
67
67
|
|
|
@@ -77,7 +77,7 @@ export default function PartnerSidecard({
|
|
|
77
77
|
height="44"
|
|
78
78
|
/>
|
|
79
79
|
)}
|
|
80
|
-
<div className={styles.tierLabel}>{
|
|
80
|
+
<div className={styles.tierLabel}>{`${tierLabel} partner`}</div>
|
|
81
81
|
<p className={styles.tierBlurb}>{TIER_BLURB[tier]}</p>
|
|
82
82
|
</div>
|
|
83
83
|
|
|
@@ -25,15 +25,17 @@
|
|
|
25
25
|
box-shadow: var(--shadow-1);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
.tier-
|
|
28
|
+
.tier-certified .tierCard {
|
|
29
|
+
/* Cobalt-fill card carries the Conduction-issued credential treatment
|
|
30
|
+
for top-tier partners. */
|
|
29
31
|
background: var(--c-blue-cobalt);
|
|
30
32
|
border-color: var(--c-blue-cobalt);
|
|
31
33
|
color: white;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
.tier-
|
|
35
|
-
/* white card, but a hairline gold stripe
|
|
36
|
-
|
|
36
|
+
.tier-service .tierCard {
|
|
37
|
+
/* white card, but a hairline gold stripe reads it as a service-level
|
|
38
|
+
SLA partner rather than a plain Host. */
|
|
37
39
|
border-top: 4px solid var(--c-gold-500);
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -53,8 +55,8 @@
|
|
|
53
55
|
margin-bottom: var(--space-2);
|
|
54
56
|
font-weight: 600;
|
|
55
57
|
}
|
|
56
|
-
.tier-
|
|
57
|
-
.tier-
|
|
58
|
+
.tier-certified .tierLabel { color: rgba(255, 255, 255, 0.78); }
|
|
59
|
+
.tier-service .tierLabel { color: var(--c-gold-600, #8B6914); }
|
|
58
60
|
|
|
59
61
|
.tierBlurb {
|
|
60
62
|
font-size: 13px;
|
|
@@ -62,7 +64,7 @@
|
|
|
62
64
|
color: var(--c-cobalt-700);
|
|
63
65
|
margin: 0;
|
|
64
66
|
}
|
|
65
|
-
.tier-
|
|
67
|
+
.tier-certified .tierBlurb { color: rgba(255, 255, 255, 0.85); }
|
|
66
68
|
|
|
67
69
|
/* ---------- Generic section ---------- */
|
|
68
70
|
|