@marlinjai/clearify 1.8.0 → 1.9.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/README.md +3 -3
- package/dist/node/{chunk-4BOXMNVR.js → chunk-77VHQV7W.js} +2 -2
- package/dist/node/{chunk-W3MZOFJQ.js → chunk-GGUDZD4K.js} +3 -3
- package/dist/node/{chunk-3WWPYCGQ.js → chunk-IF6GI4WU.js} +2 -2
- package/dist/node/{chunk-YMRRZEYA.js → chunk-PQMKRFBQ.js} +8 -3
- package/dist/node/{chunk-6PUBRHS7.js → chunk-QNC5QRIO.js} +55 -18
- package/dist/node/core/config.js +1 -1
- package/dist/node/core/navigation.js +1 -1
- package/dist/node/node/build.js +4 -4
- package/dist/node/node/check.js +3 -3
- package/dist/node/node/index.js +5 -5
- package/dist/node/types/index.d.ts +3 -0
- package/dist/node/vite-plugin/index.js +3 -3
- package/package.json +8 -7
- package/src/theme/Layout.tsx +1 -0
- package/src/theme/Sidebar.tsx +34 -2
- package/src/theme/components/ProjectGrid.tsx +121 -31
package/README.md
CHANGED
|
@@ -20,19 +20,19 @@ An open-source documentation site generator. Turn markdown into beautiful docs.
|
|
|
20
20
|
Install Clearify as a dev dependency:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
23
|
+
pnpm add -D @marlinjai/clearify
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Scaffold your project:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
|
|
29
|
+
pnpm exec clearify init
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Start the dev server:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
|
|
35
|
+
pnpm exec clearify dev
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Your docs are live at `http://localhost:4747`.
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
loadUserConfig,
|
|
3
3
|
resolveConfig,
|
|
4
4
|
resolveSections
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PQMKRFBQ.js";
|
|
6
6
|
import {
|
|
7
7
|
scanDocs
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QNC5QRIO.js";
|
|
9
9
|
|
|
10
10
|
// src/node/check.ts
|
|
11
11
|
import { resolve } from "path";
|
|
@@ -8,15 +8,15 @@ import {
|
|
|
8
8
|
} from "./chunk-SCZZB7OE.js";
|
|
9
9
|
import {
|
|
10
10
|
clearifyPlugin
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IF6GI4WU.js";
|
|
12
12
|
import {
|
|
13
13
|
loadUserConfig,
|
|
14
14
|
resolveConfig,
|
|
15
15
|
resolveSections
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-PQMKRFBQ.js";
|
|
17
17
|
import {
|
|
18
18
|
buildSectionData
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QNC5QRIO.js";
|
|
20
20
|
|
|
21
21
|
// src/node/build.ts
|
|
22
22
|
import { build as viteBuild } from "vite";
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
resolveConfig,
|
|
7
7
|
resolveSections,
|
|
8
8
|
scanHubProjects
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PQMKRFBQ.js";
|
|
10
10
|
import {
|
|
11
11
|
buildSectionData
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QNC5QRIO.js";
|
|
13
13
|
|
|
14
14
|
// src/vite-plugin/index.ts
|
|
15
15
|
import { resolve } from "path";
|
|
@@ -38,7 +38,8 @@ var HubProjectSchema = z.object({
|
|
|
38
38
|
repo: z.string().optional(),
|
|
39
39
|
status: z.enum(["active", "beta", "planned", "deprecated"]).default("active"),
|
|
40
40
|
icon: z.string().optional(),
|
|
41
|
-
tags: z.array(z.string()).optional()
|
|
41
|
+
tags: z.array(z.string()).optional(),
|
|
42
|
+
group: z.string().optional()
|
|
42
43
|
});
|
|
43
44
|
var HubProjectPartialSchema = z.object({
|
|
44
45
|
description: z.string(),
|
|
@@ -46,7 +47,10 @@ var HubProjectPartialSchema = z.object({
|
|
|
46
47
|
repo: z.string().optional(),
|
|
47
48
|
status: z.enum(["active", "beta", "planned", "deprecated"]).default("active"),
|
|
48
49
|
icon: z.string().optional(),
|
|
49
|
-
tags: z.array(z.string()).optional()
|
|
50
|
+
tags: z.array(z.string()).optional(),
|
|
51
|
+
group: z.string().optional(),
|
|
52
|
+
hubUrl: z.string().optional(),
|
|
53
|
+
hubName: z.string().optional()
|
|
50
54
|
}).optional();
|
|
51
55
|
var HubConfigSchema = z.object({
|
|
52
56
|
projects: z.array(HubProjectSchema).default([]),
|
|
@@ -236,7 +240,8 @@ async function scanHubProjects(config, root) {
|
|
|
236
240
|
repo: hp.repo,
|
|
237
241
|
status: hp.status,
|
|
238
242
|
icon: hp.icon,
|
|
239
|
-
tags: hp.tags
|
|
243
|
+
tags: hp.tags,
|
|
244
|
+
group: hp.group
|
|
240
245
|
});
|
|
241
246
|
} catch {
|
|
242
247
|
}
|
|
@@ -47,9 +47,20 @@ function scanDocs(docsDir, exclude = [], basePath = "/") {
|
|
|
47
47
|
}
|
|
48
48
|
function buildNavigation(docs, basePath = "/") {
|
|
49
49
|
const rootDir = basePath === "/" ? "/" : basePath.replace(/\/$/, "");
|
|
50
|
-
const
|
|
50
|
+
const indexPages = /* @__PURE__ */ new Map();
|
|
51
|
+
const nonIndexDocs = [];
|
|
51
52
|
for (const doc of docs) {
|
|
52
53
|
if (doc.routePath === rootDir || doc.routePath === "/") continue;
|
|
54
|
+
const parentDir = dirname(doc.routePath);
|
|
55
|
+
const fileName = basename(doc.filePath, extname(doc.filePath));
|
|
56
|
+
if (fileName === "index") {
|
|
57
|
+
indexPages.set(doc.routePath, doc);
|
|
58
|
+
} else {
|
|
59
|
+
nonIndexDocs.push(doc);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const groups = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const doc of nonIndexDocs) {
|
|
53
64
|
const dir = dirname(doc.routePath);
|
|
54
65
|
if (!groups.has(dir)) groups.set(dir, []);
|
|
55
66
|
groups.get(dir).push(doc);
|
|
@@ -62,25 +73,51 @@ function buildNavigation(docs, basePath = "/") {
|
|
|
62
73
|
return a.routePath.localeCompare(b.routePath);
|
|
63
74
|
});
|
|
64
75
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
function buildGroupChildren(parentDir) {
|
|
77
|
+
const items = [];
|
|
78
|
+
const directFiles = groups.get(parentDir) ?? [];
|
|
79
|
+
for (const doc of directFiles) {
|
|
80
|
+
const item = { label: doc.frontmatter.title, path: doc.routePath };
|
|
81
|
+
if (doc.frontmatter.icon) item.icon = doc.frontmatter.icon;
|
|
82
|
+
items.push(item);
|
|
83
|
+
}
|
|
84
|
+
const childDirs = /* @__PURE__ */ new Set();
|
|
85
|
+
for (const dir of groups.keys()) {
|
|
86
|
+
if (dir !== parentDir && dirname(dir) === parentDir) {
|
|
87
|
+
childDirs.add(dir);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const indexPath of indexPages.keys()) {
|
|
91
|
+
const dir = indexPath;
|
|
92
|
+
if (dirname(dir) === parentDir && !childDirs.has(dir)) {
|
|
93
|
+
childDirs.add(dir);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const sortedChildDirs = [...childDirs].sort((a, b) => {
|
|
97
|
+
const indexA = indexPages.get(a);
|
|
98
|
+
const indexB = indexPages.get(b);
|
|
99
|
+
const orderA = indexA?.frontmatter.order ?? 999;
|
|
100
|
+
const orderB = indexB?.frontmatter.order ?? 999;
|
|
101
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
102
|
+
return a.localeCompare(b);
|
|
80
103
|
});
|
|
81
|
-
|
|
104
|
+
for (const childDir of sortedChildDirs) {
|
|
105
|
+
const indexPage = indexPages.get(childDir);
|
|
106
|
+
const label = indexPage?.frontmatter.title ?? toTitleCase(basename(childDir));
|
|
107
|
+
const children = buildGroupChildren(childDir);
|
|
108
|
+
if (children.length > 0) {
|
|
109
|
+
const group = { label, children };
|
|
110
|
+
if (indexPage?.frontmatter.icon) group.icon = indexPage.frontmatter.icon;
|
|
111
|
+
items.push(group);
|
|
112
|
+
} else if (indexPage) {
|
|
113
|
+
const item = { label, path: indexPage.routePath };
|
|
114
|
+
if (indexPage.frontmatter.icon) item.icon = indexPage.frontmatter.icon;
|
|
115
|
+
items.push(item);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return items;
|
|
82
119
|
}
|
|
83
|
-
return
|
|
120
|
+
return buildGroupChildren(rootDir);
|
|
84
121
|
}
|
|
85
122
|
function buildRoutes(docs, sectionId) {
|
|
86
123
|
return docs.map((doc) => ({
|
package/dist/node/core/config.js
CHANGED
package/dist/node/node/build.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-GGUDZD4K.js";
|
|
4
4
|
import "../chunk-PRTER35L.js";
|
|
5
5
|
import "../chunk-SCZZB7OE.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-IF6GI4WU.js";
|
|
7
7
|
import "../chunk-B2Q23JW3.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-PQMKRFBQ.js";
|
|
9
9
|
import "../chunk-V7LLYIRO.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-QNC5QRIO.js";
|
|
11
11
|
import "../chunk-5TD7NQIW.js";
|
|
12
12
|
export {
|
|
13
13
|
buildSite
|
package/dist/node/node/check.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkLinks
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-77VHQV7W.js";
|
|
4
|
+
import "../chunk-PQMKRFBQ.js";
|
|
5
5
|
import "../chunk-V7LLYIRO.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-QNC5QRIO.js";
|
|
7
7
|
import "../chunk-5TD7NQIW.js";
|
|
8
8
|
export {
|
|
9
9
|
checkLinks
|
package/dist/node/node/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-GGUDZD4K.js";
|
|
4
4
|
import {
|
|
5
5
|
remarkMermaidToComponent
|
|
6
6
|
} from "../chunk-PRTER35L.js";
|
|
7
7
|
import "../chunk-SCZZB7OE.js";
|
|
8
8
|
import {
|
|
9
9
|
clearifyPlugin
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-IF6GI4WU.js";
|
|
11
11
|
import "../chunk-B2Q23JW3.js";
|
|
12
12
|
import {
|
|
13
13
|
init
|
|
14
14
|
} from "../chunk-QIAMD2Q6.js";
|
|
15
15
|
import {
|
|
16
16
|
checkLinks
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-77VHQV7W.js";
|
|
18
18
|
import {
|
|
19
19
|
loadUserConfig,
|
|
20
20
|
resolveConfig
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-PQMKRFBQ.js";
|
|
22
22
|
import "../chunk-V7LLYIRO.js";
|
|
23
|
-
import "../chunk-
|
|
23
|
+
import "../chunk-QNC5QRIO.js";
|
|
24
24
|
import "../chunk-5TD7NQIW.js";
|
|
25
25
|
|
|
26
26
|
// src/node/index.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clearifyPlugin
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-IF6GI4WU.js";
|
|
4
4
|
import "../chunk-B2Q23JW3.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-PQMKRFBQ.js";
|
|
6
6
|
import "../chunk-V7LLYIRO.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-QNC5QRIO.js";
|
|
8
8
|
import "../chunk-5TD7NQIW.js";
|
|
9
9
|
export {
|
|
10
10
|
clearifyPlugin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marlinjai/clearify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "An open-source documentation site generator. Turn markdown into beautiful docs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"src/client",
|
|
23
23
|
"src/theme"
|
|
24
24
|
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev:self": "node bin/clearify.js dev"
|
|
28
|
+
},
|
|
25
29
|
"dependencies": {
|
|
26
30
|
"@mdx-js/react": "^3.1.1",
|
|
27
31
|
"@mdx-js/rollup": "^3.1.1",
|
|
@@ -68,6 +72,7 @@
|
|
|
68
72
|
"tsup": "^8.3.6",
|
|
69
73
|
"typescript": "^5.7.3"
|
|
70
74
|
},
|
|
75
|
+
"packageManager": "pnpm@9.15.0",
|
|
71
76
|
"license": "MIT",
|
|
72
77
|
"author": "MarlinJai",
|
|
73
78
|
"repository": {
|
|
@@ -88,9 +93,5 @@
|
|
|
88
93
|
"react",
|
|
89
94
|
"mermaid",
|
|
90
95
|
"openapi"
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
"build": "tsup",
|
|
94
|
-
"dev:self": "node bin/clearify.js dev"
|
|
95
|
-
}
|
|
96
|
-
}
|
|
96
|
+
]
|
|
97
|
+
}
|
package/src/theme/Layout.tsx
CHANGED
package/src/theme/Sidebar.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import type { NavigationItem, SectionNavigation } from '../types/index.js';
|
|
4
|
+
import type { NavigationItem, SectionNavigation, HubProject } from '../types/index.js';
|
|
5
5
|
|
|
6
6
|
interface SidebarProps {
|
|
7
7
|
sections: SectionNavigation[];
|
|
8
8
|
open: boolean;
|
|
9
9
|
onClose: () => void;
|
|
10
|
+
hubProject?: Omit<HubProject, 'name'>;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function findActiveSection(pathname: string, sections: SectionNavigation[]): string {
|
|
@@ -103,7 +104,7 @@ function SectionSwitcher({
|
|
|
103
104
|
);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
export function Sidebar({ sections, open, onClose }: SidebarProps) {
|
|
107
|
+
export function Sidebar({ sections, open, onClose, hubProject }: SidebarProps) {
|
|
107
108
|
const location = useLocation();
|
|
108
109
|
const navigate = useNavigate();
|
|
109
110
|
|
|
@@ -246,6 +247,37 @@ export function Sidebar({ sections, open, onClose }: SidebarProps) {
|
|
|
246
247
|
}}
|
|
247
248
|
className={clsx('clearify-sidebar', open && 'clearify-sidebar-open')}
|
|
248
249
|
>
|
|
250
|
+
{hubProject?.hubUrl && (
|
|
251
|
+
<a
|
|
252
|
+
href={hubProject.hubUrl}
|
|
253
|
+
style={{
|
|
254
|
+
display: 'flex',
|
|
255
|
+
alignItems: 'center',
|
|
256
|
+
gap: '0.375rem',
|
|
257
|
+
padding: '0.375rem 0.75rem',
|
|
258
|
+
margin: '0 0.5rem 0.5rem',
|
|
259
|
+
fontSize: '0.75rem',
|
|
260
|
+
fontWeight: 500,
|
|
261
|
+
color: 'var(--clearify-text-tertiary)',
|
|
262
|
+
textDecoration: 'none',
|
|
263
|
+
borderRadius: 'var(--clearify-radius-sm)',
|
|
264
|
+
transition: 'color 0.15s, background-color 0.15s',
|
|
265
|
+
}}
|
|
266
|
+
onMouseEnter={(e) => {
|
|
267
|
+
e.currentTarget.style.color = 'var(--clearify-primary)';
|
|
268
|
+
e.currentTarget.style.backgroundColor = 'var(--clearify-primary-soft)';
|
|
269
|
+
}}
|
|
270
|
+
onMouseLeave={(e) => {
|
|
271
|
+
e.currentTarget.style.color = 'var(--clearify-text-tertiary)';
|
|
272
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
276
|
+
<polyline points="15 18 9 12 15 6" />
|
|
277
|
+
</svg>
|
|
278
|
+
{hubProject.hubName ?? 'Hub'}
|
|
279
|
+
</a>
|
|
280
|
+
)}
|
|
249
281
|
{showSwitcher && (
|
|
250
282
|
<SectionSwitcher
|
|
251
283
|
sections={sections}
|
|
@@ -1,52 +1,142 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import hub from 'virtual:clearify/hub';
|
|
3
3
|
import { ProjectCard } from './ProjectCard.js';
|
|
4
|
+
import type { HubProject } from '../../types/index.js';
|
|
4
5
|
|
|
5
6
|
interface ProjectGridProps {
|
|
6
7
|
cols?: number;
|
|
7
8
|
children?: React.ReactNode;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
const hasChildren = React.Children.count(children) > 0;
|
|
12
|
-
|
|
11
|
+
function ProjectGridInner({ cols, projects }: { cols: number; projects: HubProject[] }) {
|
|
13
12
|
return (
|
|
14
13
|
<div
|
|
15
14
|
style={{
|
|
16
15
|
display: 'grid',
|
|
17
16
|
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
18
17
|
gap: '0.875rem',
|
|
19
|
-
marginBottom: '1.5rem',
|
|
20
18
|
}}
|
|
21
19
|
className="clearify-project-grid"
|
|
22
20
|
>
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/>
|
|
36
|
-
))}
|
|
37
|
-
|
|
38
|
-
<style>{`
|
|
39
|
-
@media (max-width: 900px) {
|
|
40
|
-
.clearify-project-grid {
|
|
41
|
-
grid-template-columns: repeat(2, 1fr) !important;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
@media (max-width: 640px) {
|
|
45
|
-
.clearify-project-grid {
|
|
46
|
-
grid-template-columns: 1fr !important;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
`}</style>
|
|
21
|
+
{projects.map((project) => (
|
|
22
|
+
<ProjectCard
|
|
23
|
+
key={project.name}
|
|
24
|
+
name={project.name}
|
|
25
|
+
description={project.description}
|
|
26
|
+
href={project.href}
|
|
27
|
+
repo={project.repo}
|
|
28
|
+
status={project.status}
|
|
29
|
+
icon={project.icon}
|
|
30
|
+
tags={project.tags}
|
|
31
|
+
/>
|
|
32
|
+
))}
|
|
50
33
|
</div>
|
|
51
34
|
);
|
|
52
35
|
}
|
|
36
|
+
|
|
37
|
+
export function ProjectGrid({ cols = 3, children }: ProjectGridProps) {
|
|
38
|
+
const hasChildren = React.Children.count(children) > 0;
|
|
39
|
+
|
|
40
|
+
if (hasChildren) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
style={{
|
|
44
|
+
display: 'grid',
|
|
45
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
46
|
+
gap: '0.875rem',
|
|
47
|
+
marginBottom: '1.5rem',
|
|
48
|
+
}}
|
|
49
|
+
className="clearify-project-grid"
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
<style>{gridResponsiveStyles}</style>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const projects: HubProject[] = hub?.projects ?? [];
|
|
58
|
+
const hasGroups = projects.some((p) => p.group);
|
|
59
|
+
|
|
60
|
+
if (!hasGroups) {
|
|
61
|
+
return (
|
|
62
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
63
|
+
<ProjectGridInner cols={cols} projects={projects} />
|
|
64
|
+
<style>{gridResponsiveStyles}</style>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Group projects by their group field, preserving insertion order
|
|
70
|
+
const groupOrder: string[] = [];
|
|
71
|
+
const grouped: Record<string, HubProject[]> = {};
|
|
72
|
+
const ungrouped: HubProject[] = [];
|
|
73
|
+
|
|
74
|
+
for (const project of projects) {
|
|
75
|
+
if (project.group) {
|
|
76
|
+
if (!grouped[project.group]) {
|
|
77
|
+
grouped[project.group] = [];
|
|
78
|
+
groupOrder.push(project.group);
|
|
79
|
+
}
|
|
80
|
+
grouped[project.group].push(project);
|
|
81
|
+
} else {
|
|
82
|
+
ungrouped.push(project);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
88
|
+
{groupOrder.map((groupName) => (
|
|
89
|
+
<div key={groupName} style={{ marginBottom: '1.5rem' }}>
|
|
90
|
+
<h2
|
|
91
|
+
style={{
|
|
92
|
+
fontSize: '1.25rem',
|
|
93
|
+
fontWeight: 600,
|
|
94
|
+
marginBottom: '0.75rem',
|
|
95
|
+
marginTop: 0,
|
|
96
|
+
color: 'var(--clearify-text)',
|
|
97
|
+
letterSpacing: '-0.01em',
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
{groupName}
|
|
101
|
+
</h2>
|
|
102
|
+
<ProjectGridInner cols={cols} projects={grouped[groupName]} />
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
|
|
106
|
+
{ungrouped.length > 0 && (
|
|
107
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
108
|
+
{groupOrder.length > 0 && (
|
|
109
|
+
<h2
|
|
110
|
+
style={{
|
|
111
|
+
fontSize: '1.25rem',
|
|
112
|
+
fontWeight: 600,
|
|
113
|
+
marginBottom: '0.75rem',
|
|
114
|
+
marginTop: 0,
|
|
115
|
+
color: 'var(--clearify-text)',
|
|
116
|
+
letterSpacing: '-0.01em',
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
Other Projects
|
|
120
|
+
</h2>
|
|
121
|
+
)}
|
|
122
|
+
<ProjectGridInner cols={cols} projects={ungrouped} />
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
<style>{gridResponsiveStyles}</style>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const gridResponsiveStyles = `
|
|
132
|
+
@media (max-width: 900px) {
|
|
133
|
+
.clearify-project-grid {
|
|
134
|
+
grid-template-columns: repeat(2, 1fr) !important;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
@media (max-width: 640px) {
|
|
138
|
+
.clearify-project-grid {
|
|
139
|
+
grid-template-columns: 1fr !important;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`;
|