@conduction/docusaurus-preset 3.24.0 → 3.25.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
CHANGED
|
@@ -47,20 +47,29 @@ import styles from './FeatureGrid.module.css';
|
|
|
47
47
|
|
|
48
48
|
const STATUS_CLASSES = {stable: '', beta: styles.beta, soon: styles.soon};
|
|
49
49
|
|
|
50
|
-
export function FeatureItem({label, tip, status = 'stable', href, className}) {
|
|
50
|
+
export function FeatureItem({label, tip, status = 'stable', href, className, showDescription = false}) {
|
|
51
51
|
const hexClass = [styles.h, STATUS_CLASSES[status]].filter(Boolean).join(' ');
|
|
52
|
-
const body = (
|
|
52
|
+
const body = showDescription ? (
|
|
53
|
+
<>
|
|
54
|
+
<span className={styles.head}>
|
|
55
|
+
<span className={hexClass} aria-hidden="true" />
|
|
56
|
+
<span className={styles.label}>{label}</span>
|
|
57
|
+
</span>
|
|
58
|
+
{tip && <span className={styles.desc}>{tip}</span>}
|
|
59
|
+
</>
|
|
60
|
+
) : (
|
|
53
61
|
<>
|
|
54
62
|
<span className={hexClass} aria-hidden="true" />
|
|
55
63
|
<span className={styles.label}>{label}</span>
|
|
56
64
|
{tip && <span className={styles.tip}>{tip}</span>}
|
|
57
65
|
</>
|
|
58
66
|
);
|
|
67
|
+
const cardClass = showDescription ? styles.itemCard : null;
|
|
59
68
|
if (href) {
|
|
60
69
|
const isExternal = /^https?:\/\//.test(href);
|
|
61
70
|
return (
|
|
62
71
|
<a
|
|
63
|
-
className={[styles.item, styles.itemLink, className].filter(Boolean).join(' ')}
|
|
72
|
+
className={[styles.item, cardClass, styles.itemLink, className].filter(Boolean).join(' ')}
|
|
64
73
|
href={href}
|
|
65
74
|
title={tip || label}
|
|
66
75
|
{...(isExternal ? {target: '_blank', rel: 'noopener noreferrer'} : {})}
|
|
@@ -71,7 +80,7 @@ export function FeatureItem({label, tip, status = 'stable', href, className}) {
|
|
|
71
80
|
}
|
|
72
81
|
return (
|
|
73
82
|
<div
|
|
74
|
-
className={[styles.item, className].filter(Boolean).join(' ')}
|
|
83
|
+
className={[styles.item, cardClass, className].filter(Boolean).join(' ')}
|
|
75
84
|
tabIndex={0}
|
|
76
85
|
title={tip || label}
|
|
77
86
|
>
|
|
@@ -84,16 +93,17 @@ export function FeatureGridGroup({label, className}) {
|
|
|
84
93
|
return <h4 className={[styles.group, className].filter(Boolean).join(' ')}>{label}</h4>;
|
|
85
94
|
}
|
|
86
95
|
|
|
87
|
-
export default function FeatureGrid({items, legend = false, children, className}) {
|
|
96
|
+
export default function FeatureGrid({items, legend = false, children, className, withDescriptions = false}) {
|
|
97
|
+
const gridClass = [styles.grid, withDescriptions ? styles.gridCards : null].filter(Boolean).join(' ');
|
|
88
98
|
return (
|
|
89
99
|
<div className={className}>
|
|
90
100
|
{legend && <Legend />}
|
|
91
|
-
<div className={
|
|
101
|
+
<div className={gridClass}>
|
|
92
102
|
{items
|
|
93
103
|
? items.map((it, i) => (
|
|
94
104
|
it.group
|
|
95
105
|
? <FeatureGridGroup key={i} label={it.group} />
|
|
96
|
-
: <FeatureItem key={i} {...it} />
|
|
106
|
+
: <FeatureItem key={i} showDescription={withDescriptions} {...it} />
|
|
97
107
|
))
|
|
98
108
|
: children}
|
|
99
109
|
</div>
|
|
@@ -70,14 +70,50 @@
|
|
|
70
70
|
background: var(--c-mint-500);
|
|
71
71
|
flex-shrink: 0;
|
|
72
72
|
}
|
|
73
|
-
.beta { background: var(--c-
|
|
74
|
-
.soon { background: var(--c-
|
|
73
|
+
.beta { background: var(--c-cobalt-500); }
|
|
74
|
+
.soon { background: var(--c-orange-knvb); }
|
|
75
75
|
|
|
76
76
|
.label {
|
|
77
77
|
flex: 1;
|
|
78
78
|
line-height: 1.3;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/* Card layout (withDescriptions): hex + label on a header row, the full
|
|
82
|
+
description shown inline below instead of as a hover tooltip. */
|
|
83
|
+
.gridCards {
|
|
84
|
+
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
|
85
|
+
gap: 16px 24px;
|
|
86
|
+
}
|
|
87
|
+
.itemCard {
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
align-items: stretch;
|
|
90
|
+
gap: 6px;
|
|
91
|
+
padding: 14px 16px;
|
|
92
|
+
cursor: default;
|
|
93
|
+
border: 1px solid var(--c-cobalt-100);
|
|
94
|
+
border-radius: var(--radius-md);
|
|
95
|
+
background: white;
|
|
96
|
+
}
|
|
97
|
+
.itemCard.itemLink { cursor: pointer; }
|
|
98
|
+
.itemCard:hover,
|
|
99
|
+
.itemCard:focus-visible {
|
|
100
|
+
background: var(--c-cobalt-50);
|
|
101
|
+
border-color: var(--c-cobalt-200);
|
|
102
|
+
padding: 14px 16px;
|
|
103
|
+
margin: 0;
|
|
104
|
+
}
|
|
105
|
+
.head {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: 12px;
|
|
109
|
+
}
|
|
110
|
+
.desc {
|
|
111
|
+
font-size: 13px;
|
|
112
|
+
line-height: 1.55;
|
|
113
|
+
color: var(--c-cobalt-700);
|
|
114
|
+
margin-left: 22px;
|
|
115
|
+
}
|
|
116
|
+
|
|
81
117
|
/* Tooltip: appears above the item on hover or keyboard focus */
|
|
82
118
|
.tip {
|
|
83
119
|
position: absolute;
|
|
@@ -34,7 +34,7 @@ export default function FeaturesPage({data}) {
|
|
|
34
34
|
const items = features.map((f) => ({
|
|
35
35
|
label: f.title || f.slug,
|
|
36
36
|
tip: f.summary || '',
|
|
37
|
-
status: 'stable',
|
|
37
|
+
status: f.status || 'stable',
|
|
38
38
|
href: f.docsUrl || undefined,
|
|
39
39
|
}));
|
|
40
40
|
|
|
@@ -49,7 +49,7 @@ export default function FeaturesPage({data}) {
|
|
|
49
49
|
{items.length === 0 ? (
|
|
50
50
|
<p>No features documented yet.</p>
|
|
51
51
|
) : (
|
|
52
|
-
<FeatureGrid items={items} legend />
|
|
52
|
+
<FeatureGrid items={items} legend withDescriptions />
|
|
53
53
|
)}
|
|
54
54
|
</main>
|
|
55
55
|
</Layout>
|
|
@@ -22,6 +22,36 @@ const fs = require('fs');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
|
|
24
24
|
const FRONTMATTER_RE = /^---\s*\n([\s\S]*?\n)---\s*\n([\s\S]*)$/;
|
|
25
|
+
const SLUGGY_RE = /^[a-z0-9]+(?:-[a-z0-9]+)+$/;
|
|
26
|
+
|
|
27
|
+
// Map a spec's frontmatter status to a roadmap kind: stable (mint),
|
|
28
|
+
// beta (cobalt blue), soon (orange). Unmapped statuses are skipped.
|
|
29
|
+
const STATUS_KIND = {
|
|
30
|
+
done: 'stable', implemented: 'stable', reviewed: 'stable', active: 'stable', stable: 'stable',
|
|
31
|
+
'in-progress': 'beta', implementing: 'beta', partial: 'beta', beta: 'beta',
|
|
32
|
+
draft: 'soon', specified: 'soon', proposed: 'soon', planned: 'soon', 'coming-soon': 'soon', soon: 'soon',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const ACRONYMS = new Set([
|
|
36
|
+
'ai', 'api', 'ui', 'ux', 'or', 'bi', 'mcp', 'tmlo', 'mdto', 'dcat', 'woo',
|
|
37
|
+
'vth', 'kcc', 'crm', 'pdf', 'csv', 'sepa', 'zgw', 'ztc', 'dso', 'rbac',
|
|
38
|
+
'gdpr', 'avg', 'kvk', 'brp', 'sso', 'jwt', 'cli', 'ocr', 'ner', 'kpi',
|
|
39
|
+
'saas', 'oas', 'json', 'xml', 'html', 'css', 'url', 'http', 'https', 'id',
|
|
40
|
+
'pwa', 'sip', 'eml', 'sla', 'llm', 'rag', 'e2e', 'qr', 'vng',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function titlecaseSlug(slug) {
|
|
44
|
+
return slug
|
|
45
|
+
.split('-')
|
|
46
|
+
.map((w) => (ACRONYMS.has(w) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1)))
|
|
47
|
+
.join(' ');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function cleanTitle(rawTitle, slug) {
|
|
51
|
+
let title = rawTitle.replace(/^\s*spec:\s*/i, '').replace(/\s+specification\s*$/i, '').trim();
|
|
52
|
+
if (!title || title === slug || SLUGGY_RE.test(title)) title = titlecaseSlug(slug);
|
|
53
|
+
return title;
|
|
54
|
+
}
|
|
25
55
|
|
|
26
56
|
function parseSpec(specPath, slug) {
|
|
27
57
|
let text;
|
|
@@ -38,11 +68,12 @@ function parseSpec(specPath, slug) {
|
|
|
38
68
|
|
|
39
69
|
const statusMatch = front.match(/^status:\s*(.+?)\s*$/m);
|
|
40
70
|
const status = statusMatch ? statusMatch[1].trim().replace(/^["']|["']$/g, '').toLowerCase() : '';
|
|
41
|
-
|
|
71
|
+
const kind = STATUS_KIND[status];
|
|
72
|
+
if (!kind) return null;
|
|
42
73
|
|
|
43
74
|
const titleMatch = body.match(/^#\s+(.+?)\s*$/m);
|
|
44
75
|
const rawTitle = titleMatch ? titleMatch[1].trim() : slug;
|
|
45
|
-
const title = rawTitle
|
|
76
|
+
const title = cleanTitle(rawTitle, slug);
|
|
46
77
|
|
|
47
78
|
let summary = '';
|
|
48
79
|
const purposeHeading = body.match(/^##\s+Purpose\s*$/m);
|
|
@@ -58,6 +89,7 @@ function parseSpec(specPath, slug) {
|
|
|
58
89
|
slug,
|
|
59
90
|
title,
|
|
60
91
|
summary,
|
|
92
|
+
status: kind,
|
|
61
93
|
docsUrl: `openspec/specs/${slug}/spec.md`,
|
|
62
94
|
};
|
|
63
95
|
}
|