@raystack/chronicle 0.5.3 → 0.6.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/dist/cli/index.js +260 -81
- package/package.json +8 -6
- package/src/cli/commands/build.ts +5 -8
- package/src/cli/commands/dev.ts +5 -6
- package/src/cli/commands/init.test.ts +77 -0
- package/src/cli/commands/init.ts +73 -40
- package/src/cli/commands/serve.ts +6 -9
- package/src/cli/commands/start.ts +5 -5
- package/src/cli/utils/config.ts +6 -12
- package/src/cli/utils/scaffold.test.ts +179 -0
- package/src/cli/utils/scaffold.ts +70 -9
- package/src/components/api/field-row.tsx +1 -1
- package/src/components/api/field-section.tsx +2 -2
- package/src/components/mdx/index.tsx +1 -1
- package/src/components/mdx/mermaid.tsx +24 -21
- package/src/components/ui/breadcrumbs.tsx +4 -2
- package/src/components/ui/client-theme-switcher.tsx +21 -4
- package/src/components/ui/search.module.css +16 -41
- package/src/components/ui/search.tsx +30 -41
- package/src/lib/config.test.ts +493 -0
- package/src/lib/config.ts +123 -22
- package/src/lib/head.tsx +23 -5
- package/src/lib/llms.test.ts +94 -0
- package/src/lib/llms.ts +41 -0
- package/src/lib/navigation.test.ts +94 -0
- package/src/lib/navigation.ts +51 -0
- package/src/lib/page-context.tsx +79 -32
- package/src/lib/route-resolver.test.ts +173 -0
- package/src/lib/route-resolver.ts +73 -0
- package/src/lib/source.ts +94 -1
- package/src/lib/version-source.test.ts +163 -0
- package/src/lib/version-source.ts +101 -0
- package/src/pages/ApiPage.tsx +1 -1
- package/src/pages/DocsLayout.tsx +24 -3
- package/src/pages/DocsPage.tsx +7 -7
- package/src/pages/LandingPage.module.css +56 -0
- package/src/pages/LandingPage.tsx +39 -0
- package/src/pages/NotFound.module.css +3 -0
- package/src/pages/NotFound.tsx +9 -12
- package/src/server/App.tsx +21 -23
- package/src/server/api/{page/[...slug].ts → page.ts} +7 -3
- package/src/server/api/search.ts +51 -24
- package/src/server/api/specs.ts +17 -5
- package/src/server/entry-client.tsx +42 -14
- package/src/server/entry-server.tsx +35 -13
- package/src/server/plugins/telemetry.ts +47 -7
- package/src/server/routes/[...slug].md.ts +0 -6
- package/src/server/routes/[version]/llms.txt.ts +26 -0
- package/src/server/routes/llms.txt.ts +10 -13
- package/src/server/routes/og.tsx +2 -2
- package/src/server/routes/sitemap.xml.ts +14 -6
- package/src/server/vite-config.ts +5 -5
- package/src/themes/default/ContentDirButtons.tsx +66 -0
- package/src/themes/default/Layout.module.css +187 -40
- package/src/themes/default/Layout.tsx +166 -65
- package/src/themes/default/OpenInAI.tsx +112 -0
- package/src/themes/default/Page.module.css +30 -0
- package/src/themes/default/Page.tsx +1 -3
- package/src/themes/default/SidebarLogo.tsx +26 -0
- package/src/themes/default/Toc.module.css +102 -25
- package/src/themes/default/Toc.tsx +56 -10
- package/src/themes/default/VersionSwitcher.tsx +59 -0
- package/src/themes/paper/ContentDirDropdown.tsx +47 -0
- package/src/themes/paper/Layout.module.css +7 -0
- package/src/themes/paper/Layout.tsx +20 -13
- package/src/themes/paper/VersionSwitcher.tsx +60 -0
- package/src/types/config.ts +146 -23
- package/src/types/content.ts +11 -1
- package/src/types/theme.ts +1 -0
- package/src/components/ui/footer.module.css +0 -27
- package/src/components/ui/footer.tsx +0 -30
- package/src/server/api/metrics.ts +0 -23
- package/src/server/api/page/index.ts +0 -1
- package/src/server/telemetry.ts +0 -49
|
@@ -18,7 +18,6 @@ function resolveOutputDir(projectRoot: string, preset?: string): string {
|
|
|
18
18
|
export interface ViteConfigOptions {
|
|
19
19
|
packageRoot: string;
|
|
20
20
|
projectRoot: string;
|
|
21
|
-
contentDir: string;
|
|
22
21
|
configPath?: string;
|
|
23
22
|
preset?: string;
|
|
24
23
|
}
|
|
@@ -41,8 +40,9 @@ async function readChronicleConfig(projectRoot: string, configPath?: string): Pr
|
|
|
41
40
|
export async function createViteConfig(
|
|
42
41
|
options: ViteConfigOptions
|
|
43
42
|
): Promise<InlineConfig> {
|
|
44
|
-
const { packageRoot, projectRoot,
|
|
43
|
+
const { packageRoot, projectRoot, configPath, preset } = options;
|
|
45
44
|
const rawConfig = await readChronicleConfig(projectRoot, configPath);
|
|
45
|
+
const contentMirror = path.resolve(packageRoot, '.content');
|
|
46
46
|
|
|
47
47
|
return {
|
|
48
48
|
root: packageRoot,
|
|
@@ -86,8 +86,8 @@ export async function createViteConfig(
|
|
|
86
86
|
resolve: {
|
|
87
87
|
alias: {
|
|
88
88
|
'@': path.resolve(packageRoot, 'src'),
|
|
89
|
+
'tslib': 'tslib/tslib.es6.js',
|
|
89
90
|
},
|
|
90
|
-
conditions: ['module-sync', 'import', 'node'],
|
|
91
91
|
dedupe: [
|
|
92
92
|
'react',
|
|
93
93
|
'react-dom',
|
|
@@ -98,11 +98,11 @@ export async function createViteConfig(
|
|
|
98
98
|
},
|
|
99
99
|
server: {
|
|
100
100
|
fs: {
|
|
101
|
-
allow: [packageRoot, projectRoot,
|
|
101
|
+
allow: [packageRoot, projectRoot, contentMirror]
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
104
|
define: {
|
|
105
|
-
__CHRONICLE_CONTENT_DIR__: JSON.stringify(
|
|
105
|
+
__CHRONICLE_CONTENT_DIR__: JSON.stringify(contentMirror),
|
|
106
106
|
__CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot),
|
|
107
107
|
__CHRONICLE_CONFIG_RAW__: JSON.stringify(rawConfig),
|
|
108
108
|
},
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
|
2
|
+
import { Button, Menu, Flex } from '@raystack/apsara';
|
|
3
|
+
import { Link as RouterLink, useLocation, useNavigate } from 'react-router';
|
|
4
|
+
import { getLandingEntries } from '@/lib/config';
|
|
5
|
+
import { getActiveContentDir, splitContentButtons } from '@/lib/navigation';
|
|
6
|
+
import { usePageContext } from '@/lib/page-context';
|
|
7
|
+
|
|
8
|
+
const MAX_VISIBLE = 3;
|
|
9
|
+
|
|
10
|
+
export function ContentDirButtons() {
|
|
11
|
+
const { config, version } = usePageContext();
|
|
12
|
+
const { pathname } = useLocation();
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
|
|
15
|
+
const entries = getLandingEntries(config, version.dir);
|
|
16
|
+
if (entries.length <= 1) return null;
|
|
17
|
+
|
|
18
|
+
const active = getActiveContentDir(pathname, config);
|
|
19
|
+
const { visible, overflow } = splitContentButtons(entries, MAX_VISIBLE);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Flex gap='small' align='center'>
|
|
23
|
+
{visible.map(entry => (
|
|
24
|
+
<RouterLink
|
|
25
|
+
key={entry.href}
|
|
26
|
+
to={entry.href}
|
|
27
|
+
style={{ textDecoration: 'none' }}
|
|
28
|
+
>
|
|
29
|
+
<Button
|
|
30
|
+
size='small'
|
|
31
|
+
variant={active === entry.contentDir ? 'solid' : 'outline'}
|
|
32
|
+
color='neutral'
|
|
33
|
+
>
|
|
34
|
+
{entry.label}
|
|
35
|
+
</Button>
|
|
36
|
+
</RouterLink>
|
|
37
|
+
))}
|
|
38
|
+
{overflow.length > 0 ? (
|
|
39
|
+
<Menu>
|
|
40
|
+
<Menu.Trigger
|
|
41
|
+
render={
|
|
42
|
+
<Button
|
|
43
|
+
size='small'
|
|
44
|
+
variant='outline'
|
|
45
|
+
color='neutral'
|
|
46
|
+
trailingIcon={<ChevronDownIcon width={14} height={14} />}
|
|
47
|
+
/>
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
More
|
|
51
|
+
</Menu.Trigger>
|
|
52
|
+
<Menu.Content>
|
|
53
|
+
{overflow.map(entry => (
|
|
54
|
+
<Menu.Item
|
|
55
|
+
key={entry.href}
|
|
56
|
+
onClick={() => navigate(entry.href)}
|
|
57
|
+
>
|
|
58
|
+
{entry.label}
|
|
59
|
+
</Menu.Item>
|
|
60
|
+
))}
|
|
61
|
+
</Menu.Content>
|
|
62
|
+
</Menu>
|
|
63
|
+
) : null}
|
|
64
|
+
</Flex>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -1,79 +1,226 @@
|
|
|
1
1
|
.layout {
|
|
2
|
+
--navbar-height: 48px;
|
|
2
3
|
min-height: 100vh;
|
|
3
4
|
}
|
|
4
5
|
|
|
5
|
-
.header {
|
|
6
|
-
border-bottom: 1px solid var(--rs-color-border-base-primary);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.search {
|
|
10
|
-
margin-left: var(--rs-space-5);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
6
|
.body {
|
|
14
7
|
flex: 1;
|
|
15
8
|
}
|
|
16
9
|
|
|
17
10
|
.sidebar {
|
|
18
|
-
width:
|
|
11
|
+
width: 262px;
|
|
12
|
+
flex: 0 0 262px;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
19
15
|
position: sticky;
|
|
20
16
|
top: 0;
|
|
21
17
|
height: 100vh;
|
|
18
|
+
background: var(--rs-color-background-base-secondary);
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
.sidebarLogo {
|
|
22
|
+
width: 28px;
|
|
23
|
+
height: 28px;
|
|
24
|
+
object-fit: contain;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
.
|
|
30
|
-
|
|
27
|
+
.configIcon {
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
width: 16px;
|
|
32
|
+
height: 16px;
|
|
33
|
+
color: currentColor;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.configIcon svg {
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
display: block;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sidebar[data-position='left'] {
|
|
43
|
+
border-right: none;
|
|
31
44
|
padding: 0;
|
|
32
|
-
margin: 0;
|
|
33
45
|
}
|
|
34
46
|
|
|
35
|
-
.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
.sidebarNavbar {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: space-between;
|
|
51
|
+
gap: var(--rs-space-3);
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: var(--navbar-height);
|
|
54
|
+
padding: 0 var(--rs-space-5);
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
backdrop-filter: blur(1px);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.sidebarNavLogo {
|
|
60
|
+
color: var(--rs-color-foreground-base-primary);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sidebarNavActions {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: var(--rs-space-3);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sidebarMain {
|
|
70
|
+
padding: var(--rs-space-7) var(--rs-space-5);
|
|
71
|
+
gap: 0;
|
|
72
|
+
min-height: 0;
|
|
73
|
+
overflow-y: auto;
|
|
74
|
+
scrollbar-width: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.topLinks {
|
|
78
|
+
width: 100%;
|
|
79
|
+
margin-bottom: var(--rs-space-7);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.topLinkItem {
|
|
83
|
+
color: var(--rs-color-foreground-base-tertiary);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.topLinkText {
|
|
87
|
+
color: var(--rs-color-foreground-base-tertiary);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.topLinkItem[data-active='true'] {
|
|
91
|
+
background: transparent;
|
|
92
|
+
color: var(--rs-color-foreground-base-primary);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.topLinkItem[data-active='true'] .topLinkText {
|
|
96
|
+
color: var(--rs-color-foreground-base-primary);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.sidebarFooter {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: var(--rs-space-3);
|
|
103
|
+
height: var(--navbar-height);
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
padding: 0 var(--rs-space-5);
|
|
106
|
+
border-top: 0.5px solid var(--rs-color-border-base-primary);
|
|
107
|
+
background: var(--rs-color-background-base-secondary);
|
|
108
|
+
backdrop-filter: blur(7.5px);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.sidebarMain::-webkit-scrollbar {
|
|
112
|
+
display: none;
|
|
39
113
|
}
|
|
40
114
|
|
|
41
|
-
.
|
|
42
|
-
|
|
115
|
+
.sidebarHeader {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
gap: var(--rs-space-3);
|
|
120
|
+
height: var(--navbar-height);
|
|
121
|
+
box-sizing: border-box;
|
|
122
|
+
margin-bottom: 0;
|
|
123
|
+
padding: 0 var(--rs-space-5);
|
|
124
|
+
background: var(--rs-color-background-base-secondary);
|
|
125
|
+
border-bottom: 0.5px solid var(--rs-color-border-base-primary);
|
|
126
|
+
border-radius: 0;
|
|
127
|
+
backdrop-filter: blur(1px);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.mainArea {
|
|
131
|
+
flex: 1;
|
|
132
|
+
min-width: 0;
|
|
43
133
|
}
|
|
44
134
|
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
135
|
+
.cardWrapper {
|
|
136
|
+
flex: 1;
|
|
137
|
+
display: flex;
|
|
138
|
+
padding: 0 0 var(--rs-space-2) 0;
|
|
139
|
+
min-height: 0;
|
|
140
|
+
background: var(--rs-color-background-base-secondary);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.card {
|
|
144
|
+
flex: 1;
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
border-left: 0.5px solid var(--rs-color-border-base-primary);
|
|
148
|
+
box-shadow: var(--rs-shadow-soft);
|
|
149
|
+
overflow: visible;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.subNav {
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
justify-content: space-between;
|
|
156
|
+
height: var(--navbar-height);
|
|
157
|
+
padding: var(--rs-space-4) var(--rs-space-7);
|
|
158
|
+
background: var(--rs-color-background-base-primary);
|
|
159
|
+
border-bottom: 0.5px solid var(--rs-color-border-base-primary);
|
|
160
|
+
backdrop-filter: blur(1px);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.subNavLeft {
|
|
164
|
+
min-width: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.content {
|
|
168
|
+
flex: 1;
|
|
169
|
+
padding: var(--rs-space-9) var(--rs-space-7);
|
|
170
|
+
background: var(--rs-color-background-base-primary);
|
|
51
171
|
}
|
|
52
172
|
|
|
53
|
-
.
|
|
54
|
-
margin-top: var(--rs-space-2);
|
|
173
|
+
.groupItems {
|
|
55
174
|
padding-left: var(--rs-space-4);
|
|
175
|
+
padding-bottom: var(--rs-space-3);
|
|
176
|
+
gap: 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.navGroup {
|
|
180
|
+
margin-top: 0;
|
|
56
181
|
}
|
|
57
182
|
|
|
58
|
-
.
|
|
183
|
+
.navGroup[data-depth='0'] {
|
|
184
|
+
margin-top: var(--rs-space-7);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.navGroup .navGroupHeader {
|
|
188
|
+
margin: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.navGroup[data-depth='1'] .navGroupHeader {
|
|
192
|
+
height: auto;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.navGroup[data-depth='1'] .navGroupLabel {
|
|
196
|
+
color: var(--rs-color-foreground-base-primary);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.navGroupTrigger {
|
|
59
200
|
display: flex;
|
|
201
|
+
padding: var(--rs-space-3);
|
|
60
202
|
align-items: center;
|
|
203
|
+
gap: var(--rs-space-3);
|
|
204
|
+
align-self: stretch;
|
|
205
|
+
width: 100%;
|
|
61
206
|
height: 32px;
|
|
62
|
-
padding: 0 var(--rs-space-4);
|
|
63
|
-
border: 1px solid var(--rs-color-border-base-primary);
|
|
64
207
|
border-radius: var(--rs-radius-2);
|
|
65
|
-
|
|
66
|
-
font-weight: var(--rs-font-weight-medium);
|
|
67
|
-
color: var(--rs-color-foreground-base-primary);
|
|
68
|
-
text-decoration: none;
|
|
208
|
+
box-sizing: border-box;
|
|
69
209
|
}
|
|
70
210
|
|
|
71
|
-
.
|
|
72
|
-
|
|
211
|
+
.navGroupLabel {
|
|
212
|
+
flex: 1;
|
|
213
|
+
text-align: left;
|
|
214
|
+
color: var(--rs-color-foreground-base-secondary);
|
|
215
|
+
font-family: var(--rs-font-body);
|
|
216
|
+
font-size: var(--rs-font-size-small);
|
|
217
|
+
font-weight: var(--rs-font-weight-medium);
|
|
218
|
+
line-height: var(--rs-line-height-small);
|
|
219
|
+
letter-spacing: var(--rs-letter-spacing-small);
|
|
73
220
|
}
|
|
74
221
|
|
|
75
|
-
.
|
|
76
|
-
|
|
222
|
+
.navGroupChevron {
|
|
223
|
+
margin-left: auto;
|
|
77
224
|
}
|
|
78
225
|
|
|
79
226
|
.page {
|
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
import { RectangleStackIcon } from '@heroicons/react/24/outline';
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '@
|
|
2
|
+
ArrowLeftIcon,
|
|
3
|
+
ArrowRightIcon,
|
|
4
|
+
CodeBracketSquareIcon,
|
|
5
|
+
RectangleStackIcon,
|
|
6
|
+
DocumentTextIcon,
|
|
7
|
+
Squares2X2Icon
|
|
8
|
+
} from '@heroicons/react/24/outline';
|
|
9
|
+
import { Flex, IconButton, Sidebar } from '@raystack/apsara';
|
|
10
10
|
import { cx } from 'class-variance-authority';
|
|
11
|
-
import { useEffect, useRef } from 'react';
|
|
12
|
-
import { Link as RouterLink, useLocation } from 'react-router';
|
|
11
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
12
|
+
import { Link as RouterLink, useLocation, useNavigate } from 'react-router';
|
|
13
13
|
import { MethodBadge } from '@/components/api/method-badge';
|
|
14
14
|
import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher';
|
|
15
|
-
import { Footer } from '@/components/ui/footer';
|
|
16
15
|
import { Search } from '@/components/ui/search';
|
|
16
|
+
import { Breadcrumbs } from '@/components/ui/breadcrumbs';
|
|
17
|
+
import { getLandingEntries } from '@/lib/config';
|
|
18
|
+
import { getActiveContentDir } from '@/lib/navigation';
|
|
19
|
+
import { usePageContext } from '@/lib/page-context';
|
|
17
20
|
import type { Node } from 'fumadocs-core/page-tree';
|
|
18
21
|
import type { ThemeLayoutProps } from '@/types';
|
|
19
22
|
import styles from './Layout.module.css';
|
|
23
|
+
import { OpenInAI } from './OpenInAI';
|
|
24
|
+
import { SidebarLogo } from './SidebarLogo';
|
|
25
|
+
import { VersionSwitcher } from './VersionSwitcher';
|
|
20
26
|
|
|
21
27
|
const iconMap: Record<string, React.ReactNode> = {
|
|
22
28
|
'rectangle-stack': <RectangleStackIcon width={16} height={16} />,
|
|
@@ -27,16 +33,51 @@ const iconMap: Record<string, React.ReactNode> = {
|
|
|
27
33
|
'method-patch': <MethodBadge method='PATCH' size='micro' />
|
|
28
34
|
};
|
|
29
35
|
|
|
36
|
+
function renderConfigIcon(
|
|
37
|
+
icon: string | undefined,
|
|
38
|
+
alt: string,
|
|
39
|
+
fallback: React.ReactNode
|
|
40
|
+
): React.ReactNode {
|
|
41
|
+
if (!icon) return fallback;
|
|
42
|
+
if (icon.trim().startsWith('<svg')) {
|
|
43
|
+
return (
|
|
44
|
+
<span
|
|
45
|
+
aria-label={alt}
|
|
46
|
+
className={styles.configIcon}
|
|
47
|
+
dangerouslySetInnerHTML={{ __html: icon }}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return <img src={icon} alt={alt} className={styles.configIcon} />;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
let savedScrollTop = 0;
|
|
31
55
|
|
|
32
56
|
export function Layout({
|
|
33
57
|
children,
|
|
34
58
|
config,
|
|
35
59
|
tree,
|
|
60
|
+
hideSidebar,
|
|
36
61
|
classNames
|
|
37
62
|
}: ThemeLayoutProps) {
|
|
38
63
|
const { pathname } = useLocation();
|
|
64
|
+
const navigate = useNavigate();
|
|
65
|
+
const { page, version } = usePageContext();
|
|
39
66
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
67
|
+
const isApiRoute = pathname === '/apis' || pathname.startsWith('/apis/');
|
|
68
|
+
const isApiBase = (basePath: string) =>
|
|
69
|
+
pathname === basePath || pathname.startsWith(`${basePath}/`);
|
|
70
|
+
const { prev, next } = page ?? { prev: null, next: null };
|
|
71
|
+
|
|
72
|
+
const contentEntries = getLandingEntries(config, version.dir);
|
|
73
|
+
const activeContentDir = getActiveContentDir(pathname, config);
|
|
74
|
+
const apiEntries = config.api ?? [];
|
|
75
|
+
const showTopLinks = contentEntries.length + apiEntries.length > 1;
|
|
76
|
+
|
|
77
|
+
const slug = useMemo(
|
|
78
|
+
() => (pathname === '/' ? [] : pathname.split('/').filter(Boolean)),
|
|
79
|
+
[pathname]
|
|
80
|
+
);
|
|
40
81
|
|
|
41
82
|
useEffect(() => {
|
|
42
83
|
const el = scrollRef.current;
|
|
@@ -58,87 +99,147 @@ export function Layout({
|
|
|
58
99
|
|
|
59
100
|
return (
|
|
60
101
|
<Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
|
|
61
|
-
<Navbar className={styles.header}>
|
|
62
|
-
<Navbar.Start>
|
|
63
|
-
<RouterLink
|
|
64
|
-
to='/'
|
|
65
|
-
style={{ textDecoration: 'none', color: 'inherit' }}
|
|
66
|
-
>
|
|
67
|
-
<Headline size='small' weight='medium' as='h1'>
|
|
68
|
-
{config.title}
|
|
69
|
-
</Headline>
|
|
70
|
-
</RouterLink>
|
|
71
|
-
</Navbar.Start>
|
|
72
|
-
<Navbar.End>
|
|
73
|
-
<Flex gap='medium' align='center' className={styles.navActions}>
|
|
74
|
-
{config.api?.map(api => (
|
|
75
|
-
<RouterLink
|
|
76
|
-
key={api.basePath}
|
|
77
|
-
to={api.basePath}
|
|
78
|
-
className={styles.navButton}
|
|
79
|
-
>
|
|
80
|
-
{api.name} API
|
|
81
|
-
</RouterLink>
|
|
82
|
-
))}
|
|
83
|
-
{config.navigation?.links?.map(link => (
|
|
84
|
-
<Link key={link.href} href={link.href}>
|
|
85
|
-
{link.label}
|
|
86
|
-
</Link>
|
|
87
|
-
))}
|
|
88
|
-
{config.search?.enabled && <Search />}
|
|
89
|
-
</Flex>
|
|
90
|
-
<ClientThemeSwitcher size={16} />
|
|
91
|
-
</Navbar.End>
|
|
92
|
-
</Navbar>
|
|
93
102
|
<Flex className={cx(styles.body, classNames?.body)}>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
{hideSidebar ? null : (
|
|
104
|
+
<Sidebar
|
|
105
|
+
defaultOpen
|
|
106
|
+
collapsible={false}
|
|
107
|
+
className={cx(styles.sidebar, classNames?.sidebar)}
|
|
108
|
+
>
|
|
109
|
+
<Sidebar.Header className={styles.sidebarHeader}>
|
|
110
|
+
<SidebarLogo config={config} />
|
|
111
|
+
<Flex gap='small' align='center'>
|
|
112
|
+
{config.search?.enabled && <Search />}
|
|
113
|
+
<ClientThemeSwitcher size={16} />
|
|
114
|
+
</Flex>
|
|
115
|
+
</Sidebar.Header>
|
|
116
|
+
<Sidebar.Main ref={scrollRef} className={styles.sidebarMain}>
|
|
117
|
+
{showTopLinks ? (
|
|
118
|
+
<div className={styles.topLinks}>
|
|
119
|
+
{contentEntries.map(entry => (
|
|
120
|
+
<Sidebar.Item
|
|
121
|
+
key={entry.href}
|
|
122
|
+
href={entry.href}
|
|
123
|
+
active={activeContentDir === entry.contentDir}
|
|
124
|
+
leadingIcon={renderConfigIcon(
|
|
125
|
+
entry.icon,
|
|
126
|
+
entry.label,
|
|
127
|
+
<DocumentTextIcon width={16} height={16} />
|
|
128
|
+
)}
|
|
129
|
+
classNames={{ root: styles.topLinkItem, text: styles.topLinkText }}
|
|
130
|
+
render={<RouterLink to={entry.href} />}
|
|
131
|
+
>
|
|
132
|
+
{entry.label}
|
|
133
|
+
</Sidebar.Item>
|
|
134
|
+
))}
|
|
135
|
+
{apiEntries.map(api => (
|
|
136
|
+
<Sidebar.Item
|
|
137
|
+
key={api.basePath}
|
|
138
|
+
href={api.basePath}
|
|
139
|
+
active={isApiBase(api.basePath)}
|
|
140
|
+
leadingIcon={renderConfigIcon(
|
|
141
|
+
api.icon,
|
|
142
|
+
api.name,
|
|
143
|
+
<CodeBracketSquareIcon width={16} height={16} />
|
|
144
|
+
)}
|
|
145
|
+
classNames={{ root: styles.topLinkItem, text: styles.topLinkText }}
|
|
146
|
+
render={<RouterLink to={api.basePath} />}
|
|
147
|
+
>
|
|
148
|
+
{api.name} API
|
|
149
|
+
</Sidebar.Item>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
) : null}
|
|
153
|
+
{tree.children.map((item, i) => (
|
|
154
|
+
<SidebarNode
|
|
155
|
+
key={item.type === 'page' ? item.url : (item.name?.toString() ?? i)}
|
|
156
|
+
item={item}
|
|
157
|
+
pathname={pathname}
|
|
158
|
+
/>
|
|
159
|
+
))}
|
|
160
|
+
</Sidebar.Main>
|
|
161
|
+
{config.versions?.length ? (
|
|
162
|
+
<Sidebar.Footer className={styles.sidebarFooter}>
|
|
163
|
+
<VersionSwitcher />
|
|
164
|
+
</Sidebar.Footer>
|
|
165
|
+
) : null}
|
|
166
|
+
</Sidebar>
|
|
167
|
+
)}
|
|
168
|
+
<Flex direction='column' className={styles.mainArea}>
|
|
169
|
+
<div className={styles.cardWrapper}>
|
|
170
|
+
<div className={styles.card}>
|
|
171
|
+
<nav className={styles.subNav}>
|
|
172
|
+
<Flex align='center' gap='small' className={styles.subNavLeft}>
|
|
173
|
+
<Flex align='center' gap='extra-small'>
|
|
174
|
+
<IconButton
|
|
175
|
+
size={2}
|
|
176
|
+
disabled={!prev}
|
|
177
|
+
onClick={() => prev && navigate(prev.url)}
|
|
178
|
+
aria-label='Previous page'
|
|
179
|
+
>
|
|
180
|
+
<ArrowLeftIcon width={14} height={14} />
|
|
181
|
+
</IconButton>
|
|
182
|
+
<IconButton
|
|
183
|
+
size={2}
|
|
184
|
+
disabled={!next}
|
|
185
|
+
onClick={() => next && navigate(next.url)}
|
|
186
|
+
aria-label='Next page'
|
|
187
|
+
>
|
|
188
|
+
<ArrowRightIcon width={14} height={14} />
|
|
189
|
+
</IconButton>
|
|
190
|
+
</Flex>
|
|
191
|
+
{!isApiRoute && <Breadcrumbs slug={slug} tree={tree} />}
|
|
192
|
+
</Flex>
|
|
193
|
+
<OpenInAI />
|
|
194
|
+
</nav>
|
|
195
|
+
<main className={cx(styles.content, classNames?.content)}>
|
|
196
|
+
{children}
|
|
197
|
+
</main>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</Flex>
|
|
112
201
|
</Flex>
|
|
113
|
-
<Footer config={config.footer} />
|
|
114
202
|
</Flex>
|
|
115
203
|
);
|
|
116
204
|
}
|
|
117
205
|
|
|
118
206
|
function SidebarNode({
|
|
119
207
|
item,
|
|
120
|
-
pathname
|
|
208
|
+
pathname,
|
|
209
|
+
depth = 0
|
|
121
210
|
}: {
|
|
122
211
|
item: Node;
|
|
123
212
|
pathname: string;
|
|
213
|
+
depth?: number;
|
|
124
214
|
}) {
|
|
125
215
|
if (item.type === 'separator') {
|
|
126
216
|
return null;
|
|
127
217
|
}
|
|
128
218
|
|
|
129
219
|
if (item.type === 'folder') {
|
|
220
|
+
if (depth > 1) return null;
|
|
130
221
|
const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
|
|
131
222
|
return (
|
|
132
223
|
<Sidebar.Group
|
|
224
|
+
className={styles.navGroup}
|
|
225
|
+
data-depth={depth}
|
|
133
226
|
label={item.name?.toString() ?? ''}
|
|
134
227
|
leadingIcon={icon ?? undefined}
|
|
135
|
-
|
|
228
|
+
collapsible={depth === 1}
|
|
229
|
+
classNames={{
|
|
230
|
+
items: styles.groupItems,
|
|
231
|
+
header: styles.navGroupHeader,
|
|
232
|
+
trigger: styles.navGroupTrigger,
|
|
233
|
+
label: styles.navGroupLabel,
|
|
234
|
+
chevron: styles.navGroupChevron,
|
|
235
|
+
}}
|
|
136
236
|
>
|
|
137
237
|
{item.children.map((child, i) => (
|
|
138
238
|
<SidebarNode
|
|
139
239
|
key={child.type === 'page' ? child.url : (child.name?.toString() ?? i)}
|
|
140
240
|
item={child}
|
|
141
241
|
pathname={pathname}
|
|
242
|
+
depth={depth + 1}
|
|
142
243
|
/>
|
|
143
244
|
))}
|
|
144
245
|
</Sidebar.Group>
|
|
@@ -155,7 +256,7 @@ function SidebarNode({
|
|
|
155
256
|
href={href}
|
|
156
257
|
active={isActive}
|
|
157
258
|
leadingIcon={icon ?? undefined}
|
|
158
|
-
|
|
259
|
+
render={link}
|
|
159
260
|
>
|
|
160
261
|
{item.name}
|
|
161
262
|
</Sidebar.Item>
|