@raystack/chronicle 0.5.4 → 0.6.1
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 +258 -80
- package/package.json +7 -5
- 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/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 +51 -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 +3 -6
- package/src/pages/LandingPage.module.css +56 -0
- package/src/pages/LandingPage.tsx +39 -0
- package/src/pages/NotFound.tsx +2 -0
- package/src/server/App.tsx +21 -23
- package/src/server/api/page.ts +5 -1
- 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 +33 -11
- 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 +145 -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
|
@@ -26,6 +26,36 @@
|
|
|
26
26
|
line-height: 1.4;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
.content > :is(h1, h2, h3, h4, h5, h6):first-child {
|
|
30
|
+
margin-top: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.content h1 {
|
|
34
|
+
margin-top: 0;
|
|
35
|
+
margin-bottom: var(--rs-space-10);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.content p {
|
|
39
|
+
color: var(--rs-color-foreground-base-primary);
|
|
40
|
+
font-family: var(--rs-font-body);
|
|
41
|
+
font-size: var(--rs-font-size-regular);
|
|
42
|
+
font-style: normal;
|
|
43
|
+
font-weight: var(--rs-font-weight-regular);
|
|
44
|
+
line-height: 171.429%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.content h2 {
|
|
48
|
+
margin-top: var(--rs-space-8);
|
|
49
|
+
margin-bottom: var(--rs-space-8);
|
|
50
|
+
color: var(--rs-color-foreground-base-primary);
|
|
51
|
+
font-family: var(--rs-font-title);
|
|
52
|
+
font-size: var(--rs-font-size-t3);
|
|
53
|
+
font-style: normal;
|
|
54
|
+
font-weight: var(--rs-font-weight-medium);
|
|
55
|
+
line-height: var(--rs-line-height-t3);
|
|
56
|
+
letter-spacing: var(--rs-letter-spacing-t3);
|
|
57
|
+
}
|
|
58
|
+
|
|
29
59
|
.content ul,
|
|
30
60
|
.content ol {
|
|
31
61
|
padding-left: var(--rs-space-5);
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Flex } from '@raystack/apsara';
|
|
4
|
-
import { Breadcrumbs } from '@/components/ui/breadcrumbs';
|
|
5
4
|
import type { ThemePageProps } from '@/types';
|
|
6
5
|
import styles from './Page.module.css';
|
|
7
6
|
import { Toc } from './Toc';
|
|
8
7
|
|
|
9
|
-
export function Page({ page
|
|
8
|
+
export function Page({ page }: ThemePageProps) {
|
|
10
9
|
return (
|
|
11
10
|
<Flex className={styles.page}>
|
|
12
11
|
<article className={styles.article} data-article-content>
|
|
13
|
-
<Breadcrumbs slug={page.slug} tree={tree} />
|
|
14
12
|
<div className={styles.content}>{page.content}</div>
|
|
15
13
|
</article>
|
|
16
14
|
<Toc items={page.toc} />
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { BookOpenIcon } from '@heroicons/react/24/outline';
|
|
4
|
+
import { useTheme } from '@raystack/apsara';
|
|
5
|
+
import type { ChronicleConfig } from '@/types';
|
|
6
|
+
import styles from './Layout.module.css';
|
|
7
|
+
|
|
8
|
+
interface SidebarLogoProps {
|
|
9
|
+
config: ChronicleConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SidebarLogo({ config }: SidebarLogoProps) {
|
|
13
|
+
const { resolvedTheme } = useTheme();
|
|
14
|
+
const logo = config.logo;
|
|
15
|
+
|
|
16
|
+
if (logo) {
|
|
17
|
+
const src = resolvedTheme === 'dark'
|
|
18
|
+
? logo.dark ?? logo.light
|
|
19
|
+
: logo.light ?? logo.dark;
|
|
20
|
+
if (src) {
|
|
21
|
+
return <img src={src} alt={config.site.title} className={styles.sidebarLogo} />;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return <BookOpenIcon width={28} height={28} />;
|
|
26
|
+
}
|
|
@@ -1,48 +1,125 @@
|
|
|
1
1
|
.toc {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
overflow-y: auto;
|
|
2
|
+
position: fixed;
|
|
3
|
+
right: var(--rs-space-3);
|
|
4
|
+
top: 50%;
|
|
5
|
+
transform: translateY(-50%);
|
|
6
|
+
z-index: 10;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
.
|
|
9
|
+
.markers {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
align-items: flex-end;
|
|
13
|
+
gap: var(--rs-space-4);
|
|
14
|
+
opacity: 1;
|
|
15
|
+
transition: opacity 150ms ease;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.toc:hover .markers,
|
|
19
|
+
.toc:focus-within .markers {
|
|
20
|
+
opacity: 0;
|
|
21
|
+
pointer-events: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.marker {
|
|
11
25
|
display: block;
|
|
26
|
+
height: 2px;
|
|
27
|
+
background: var(--rs-color-border-base-secondary);
|
|
28
|
+
border-radius: 1px;
|
|
29
|
+
transition:
|
|
30
|
+
width 0.15s ease,
|
|
31
|
+
background 0.15s ease;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.marker:hover {
|
|
35
|
+
background: var(--rs-color-foreground-base-secondary);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.markerActive {
|
|
39
|
+
background: var(--rs-color-border-base-emphasis);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
.panel {
|
|
44
|
+
position: absolute;
|
|
45
|
+
top: 50%;
|
|
46
|
+
right: 0;
|
|
47
|
+
transform: translateY(-50%) translateX(8px);
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: var(--rs-space-2);
|
|
51
|
+
min-width: 200px;
|
|
52
|
+
padding: var(--rs-space-3) 0;
|
|
53
|
+
background: var(--rs-color-background-base-primary);
|
|
54
|
+
border: 0.5px solid var(--rs-color-border-base-primary);
|
|
55
|
+
border-radius: var(--rs-radius-4);
|
|
56
|
+
box-shadow: var(--rs-shadow-soft);
|
|
57
|
+
opacity: 0;
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
transition:
|
|
60
|
+
opacity 150ms ease,
|
|
61
|
+
transform 150ms ease;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.toc:hover .panel,
|
|
65
|
+
.toc:focus-within .panel {
|
|
66
|
+
opacity: 1;
|
|
67
|
+
pointer-events: auto;
|
|
68
|
+
transform: translateY(-50%) translateX(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.panelHeader {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: var(--rs-space-3);
|
|
75
|
+
padding: var(--rs-space-3);
|
|
12
76
|
color: var(--rs-color-foreground-base-secondary);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
77
|
+
font-family: var(--rs-font-body);
|
|
78
|
+
font-size: var(--rs-font-size-small);
|
|
79
|
+
font-weight: var(--rs-font-weight-medium);
|
|
80
|
+
line-height: var(--rs-line-height-small);
|
|
81
|
+
letter-spacing: var(--rs-letter-spacing-small);
|
|
17
82
|
}
|
|
18
83
|
|
|
19
|
-
.
|
|
84
|
+
.panelHeaderLabel {
|
|
85
|
+
flex: 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.panelList {
|
|
20
89
|
display: flex;
|
|
21
90
|
flex-direction: column;
|
|
22
|
-
gap:
|
|
23
|
-
|
|
24
|
-
padding-left: var(--rs-space-3);
|
|
25
|
-
margin-bottom: var(--rs-space-6);
|
|
91
|
+
gap: var(--rs-space-1);
|
|
92
|
+
padding-left: var(--rs-space-8);
|
|
26
93
|
}
|
|
27
94
|
|
|
28
|
-
.
|
|
95
|
+
.panelItem {
|
|
96
|
+
display: block;
|
|
97
|
+
padding: var(--rs-space-3);
|
|
98
|
+
border-radius: var(--rs-radius-2);
|
|
29
99
|
color: var(--rs-color-foreground-base-tertiary);
|
|
30
|
-
|
|
100
|
+
font-family: var(--rs-font-body);
|
|
31
101
|
font-size: var(--rs-font-size-small);
|
|
32
|
-
|
|
33
|
-
|
|
102
|
+
font-weight: var(--rs-font-weight-medium);
|
|
103
|
+
line-height: var(--rs-line-height-small);
|
|
104
|
+
letter-spacing: var(--rs-letter-spacing-small);
|
|
105
|
+
text-decoration: none;
|
|
34
106
|
transition: color 0.15s ease;
|
|
35
107
|
}
|
|
36
108
|
|
|
37
|
-
.
|
|
109
|
+
.panelItem:hover {
|
|
38
110
|
color: var(--rs-color-foreground-base-primary);
|
|
39
111
|
}
|
|
40
112
|
|
|
41
|
-
.
|
|
113
|
+
.panelItemNested {
|
|
114
|
+
padding-left: var(--rs-space-5);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.panelItemActive {
|
|
42
118
|
color: var(--rs-color-foreground-base-primary);
|
|
43
|
-
font-weight: 500;
|
|
44
119
|
}
|
|
45
120
|
|
|
46
|
-
|
|
47
|
-
|
|
121
|
+
@media (max-width: 900px) {
|
|
122
|
+
.toc {
|
|
123
|
+
display: none;
|
|
124
|
+
}
|
|
48
125
|
}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Bars3BottomLeftIcon } from '@heroicons/react/24/outline';
|
|
4
4
|
import { AnchorProvider, useActiveAnchor } from 'fumadocs-core/toc';
|
|
5
5
|
import type { TableOfContents, TOCItemType } from 'fumadocs-core/toc';
|
|
6
|
+
import { cx } from 'class-variance-authority';
|
|
7
|
+
import { isValidElement, type ReactNode } from 'react';
|
|
6
8
|
import styles from './Toc.module.css';
|
|
7
9
|
|
|
10
|
+
function nodeToText(node: ReactNode): string {
|
|
11
|
+
if (node == null || typeof node === 'boolean') return '';
|
|
12
|
+
if (typeof node === 'string' || typeof node === 'number') return String(node);
|
|
13
|
+
if (Array.isArray(node)) return node.map(nodeToText).join('');
|
|
14
|
+
if (isValidElement(node)) {
|
|
15
|
+
const children = (node.props as { children?: ReactNode }).children;
|
|
16
|
+
return nodeToText(children);
|
|
17
|
+
}
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
interface TocProps {
|
|
9
22
|
items: TableOfContents;
|
|
10
23
|
}
|
|
@@ -23,30 +36,63 @@ export function Toc({ items }: TocProps) {
|
|
|
23
36
|
);
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
const MARKER_BASE = 8;
|
|
40
|
+
const MARKER_PER_CHAR = 1;
|
|
41
|
+
const MARKER_MAX = 40;
|
|
42
|
+
|
|
43
|
+
function markerWidth(title: ReactNode): number {
|
|
44
|
+
const len = nodeToText(title).length;
|
|
45
|
+
return Math.min(MARKER_MAX, MARKER_BASE + len * MARKER_PER_CHAR);
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
function TocContent({ items }: { items: TOCItemType[] }) {
|
|
27
49
|
const activeAnchor = useActiveAnchor();
|
|
28
50
|
|
|
29
51
|
return (
|
|
30
|
-
<aside className={styles.toc}>
|
|
31
|
-
<
|
|
32
|
-
On this page
|
|
33
|
-
</Text>
|
|
34
|
-
<nav className={styles.nav}>
|
|
52
|
+
<aside className={styles.toc} aria-label='Table of contents'>
|
|
53
|
+
<div className={styles.markers} aria-hidden='true'>
|
|
35
54
|
{items.map(item => {
|
|
36
55
|
const id = item.url.replace('#', '');
|
|
37
56
|
const isActive = activeAnchor === id;
|
|
38
|
-
const isNested = item.depth > 2;
|
|
39
57
|
return (
|
|
40
58
|
<a
|
|
41
59
|
key={item.url}
|
|
42
60
|
href={item.url}
|
|
43
|
-
|
|
61
|
+
tabIndex={-1}
|
|
62
|
+
className={cx(styles.marker, isActive && styles.markerActive)}
|
|
63
|
+
style={{ width: `${markerWidth(item.title)}px` }}
|
|
44
64
|
>
|
|
45
|
-
|
|
65
|
+
<span />
|
|
46
66
|
</a>
|
|
47
67
|
);
|
|
48
68
|
})}
|
|
49
|
-
</
|
|
69
|
+
</div>
|
|
70
|
+
<div className={styles.panel} role='presentation'>
|
|
71
|
+
<div className={styles.panelHeader}>
|
|
72
|
+
<Bars3BottomLeftIcon width={16} height={16} />
|
|
73
|
+
<span className={styles.panelHeaderLabel}>On this page</span>
|
|
74
|
+
</div>
|
|
75
|
+
<nav className={styles.panelList}>
|
|
76
|
+
{items.map(item => {
|
|
77
|
+
const id = item.url.replace('#', '');
|
|
78
|
+
const isActive = activeAnchor === id;
|
|
79
|
+
const isNested = item.depth > 2;
|
|
80
|
+
return (
|
|
81
|
+
<a
|
|
82
|
+
key={item.url}
|
|
83
|
+
href={item.url}
|
|
84
|
+
className={cx(
|
|
85
|
+
styles.panelItem,
|
|
86
|
+
isNested && styles.panelItemNested,
|
|
87
|
+
isActive && styles.panelItemActive
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{nodeToText(item.title)}
|
|
91
|
+
</a>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
94
|
+
</nav>
|
|
95
|
+
</div>
|
|
50
96
|
</aside>
|
|
51
97
|
);
|
|
52
98
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
|
2
|
+
import { Badge, Button, Menu, Flex } from '@raystack/apsara';
|
|
3
|
+
import { useNavigate } from 'react-router';
|
|
4
|
+
import { getAllVersions } from '@/lib/config';
|
|
5
|
+
import { getVersionHomeHref } from '@/lib/navigation';
|
|
6
|
+
import { usePageContext } from '@/lib/page-context';
|
|
7
|
+
|
|
8
|
+
export function VersionSwitcher() {
|
|
9
|
+
const { config, version } = usePageContext();
|
|
10
|
+
const navigate = useNavigate();
|
|
11
|
+
|
|
12
|
+
if (!config.versions?.length) return null;
|
|
13
|
+
|
|
14
|
+
const versions = getAllVersions(config);
|
|
15
|
+
const active = versions.find(v =>
|
|
16
|
+
v.isLatest ? version.dir === null : v.dir === version.dir,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Menu>
|
|
21
|
+
<Menu.Trigger
|
|
22
|
+
render={
|
|
23
|
+
<Button
|
|
24
|
+
size='small'
|
|
25
|
+
variant='outline'
|
|
26
|
+
color='neutral'
|
|
27
|
+
trailingIcon={<ChevronDownIcon width={14} height={14} />}
|
|
28
|
+
/>
|
|
29
|
+
}
|
|
30
|
+
>
|
|
31
|
+
<Flex gap='small' align='center'>
|
|
32
|
+
{active?.label ?? 'Version'}
|
|
33
|
+
{active?.badge ? (
|
|
34
|
+
<Badge variant={active.badge.variant} size='micro'>
|
|
35
|
+
{active.badge.label}
|
|
36
|
+
</Badge>
|
|
37
|
+
) : null}
|
|
38
|
+
</Flex>
|
|
39
|
+
</Menu.Trigger>
|
|
40
|
+
<Menu.Content>
|
|
41
|
+
{versions.map(v => (
|
|
42
|
+
<Menu.Item
|
|
43
|
+
key={v.dir ?? '_latest'}
|
|
44
|
+
onClick={() => navigate(getVersionHomeHref(config, v.dir))}
|
|
45
|
+
>
|
|
46
|
+
<Flex gap='small' align='center'>
|
|
47
|
+
{v.label}
|
|
48
|
+
{v.badge ? (
|
|
49
|
+
<Badge variant={v.badge.variant} size='micro'>
|
|
50
|
+
{v.badge.label}
|
|
51
|
+
</Badge>
|
|
52
|
+
) : null}
|
|
53
|
+
</Flex>
|
|
54
|
+
</Menu.Item>
|
|
55
|
+
))}
|
|
56
|
+
</Menu.Content>
|
|
57
|
+
</Menu>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
|
2
|
+
import { Button, Menu } from '@raystack/apsara';
|
|
3
|
+
import { useLocation, useNavigate } from 'react-router';
|
|
4
|
+
import { getLandingEntries } from '@/lib/config';
|
|
5
|
+
import { getActiveContentDir } from '@/lib/navigation';
|
|
6
|
+
import { usePageContext } from '@/lib/page-context';
|
|
7
|
+
|
|
8
|
+
export function ContentDirDropdown() {
|
|
9
|
+
const { config, version } = usePageContext();
|
|
10
|
+
const { pathname } = useLocation();
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
|
|
13
|
+
const entries = getLandingEntries(config, version.dir);
|
|
14
|
+
if (entries.length <= 1) return null;
|
|
15
|
+
|
|
16
|
+
const activeDir = getActiveContentDir(pathname, config);
|
|
17
|
+
const activeEntry =
|
|
18
|
+
entries.find(e => e.contentDir === activeDir) ?? entries[0];
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Menu>
|
|
22
|
+
<Menu.Trigger
|
|
23
|
+
render={
|
|
24
|
+
<Button
|
|
25
|
+
size='small'
|
|
26
|
+
variant='outline'
|
|
27
|
+
color='neutral'
|
|
28
|
+
width='100%'
|
|
29
|
+
trailingIcon={<ChevronDownIcon width={14} height={14} />}
|
|
30
|
+
/>
|
|
31
|
+
}
|
|
32
|
+
>
|
|
33
|
+
{activeEntry.label}
|
|
34
|
+
</Menu.Trigger>
|
|
35
|
+
<Menu.Content>
|
|
36
|
+
{entries.map(entry => (
|
|
37
|
+
<Menu.Item
|
|
38
|
+
key={entry.href}
|
|
39
|
+
onClick={() => navigate(entry.href)}
|
|
40
|
+
>
|
|
41
|
+
{entry.label}
|
|
42
|
+
</Menu.Item>
|
|
43
|
+
))}
|
|
44
|
+
</Menu.Content>
|
|
45
|
+
</Menu>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -2,36 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
import { Flex, Headline } from '@raystack/apsara';
|
|
4
4
|
import { cx } from 'class-variance-authority';
|
|
5
|
-
import { Footer } from '@/components/ui/footer';
|
|
6
5
|
import type { ThemeLayoutProps } from '@/types';
|
|
7
6
|
import { ChapterNav } from './ChapterNav';
|
|
7
|
+
import { ContentDirDropdown } from './ContentDirDropdown';
|
|
8
8
|
import styles from './Layout.module.css';
|
|
9
|
+
import { VersionSwitcher } from './VersionSwitcher';
|
|
9
10
|
|
|
10
11
|
export function Layout({
|
|
11
12
|
children,
|
|
12
13
|
config,
|
|
13
14
|
tree,
|
|
15
|
+
hideSidebar,
|
|
14
16
|
classNames
|
|
15
17
|
}: ThemeLayoutProps) {
|
|
16
18
|
return (
|
|
17
19
|
<Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
|
|
18
20
|
<Flex className={cx(styles.body, classNames?.body)}>
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
{hideSidebar ? null : (
|
|
22
|
+
<aside className={cx(styles.sidebar, classNames?.sidebar)}>
|
|
23
|
+
<Headline
|
|
24
|
+
size='small'
|
|
25
|
+
weight='medium'
|
|
26
|
+
as='h1'
|
|
27
|
+
className={styles.title}
|
|
28
|
+
>
|
|
29
|
+
{config.site.title}
|
|
30
|
+
</Headline>
|
|
31
|
+
<div className={styles.nav}>
|
|
32
|
+
<VersionSwitcher />
|
|
33
|
+
<ContentDirDropdown />
|
|
34
|
+
</div>
|
|
35
|
+
<ChapterNav tree={tree} />
|
|
36
|
+
</aside>
|
|
37
|
+
)}
|
|
30
38
|
<div className={cx(styles.content, classNames?.content)}>
|
|
31
39
|
{children}
|
|
32
40
|
</div>
|
|
33
41
|
</Flex>
|
|
34
|
-
<Footer config={config.footer} />
|
|
35
42
|
</Flex>
|
|
36
43
|
);
|
|
37
44
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
|
2
|
+
import { Badge, Button, Menu, Flex } from '@raystack/apsara';
|
|
3
|
+
import { useNavigate } from 'react-router';
|
|
4
|
+
import { getAllVersions } from '@/lib/config';
|
|
5
|
+
import { getVersionHomeHref } from '@/lib/navigation';
|
|
6
|
+
import { usePageContext } from '@/lib/page-context';
|
|
7
|
+
|
|
8
|
+
export function VersionSwitcher() {
|
|
9
|
+
const { config, version } = usePageContext();
|
|
10
|
+
const navigate = useNavigate();
|
|
11
|
+
|
|
12
|
+
if (!config.versions?.length) return null;
|
|
13
|
+
|
|
14
|
+
const versions = getAllVersions(config);
|
|
15
|
+
const active = versions.find(v =>
|
|
16
|
+
v.isLatest ? version.dir === null : v.dir === version.dir,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Menu>
|
|
21
|
+
<Menu.Trigger
|
|
22
|
+
render={
|
|
23
|
+
<Button
|
|
24
|
+
size='small'
|
|
25
|
+
variant='outline'
|
|
26
|
+
color='neutral'
|
|
27
|
+
width='100%'
|
|
28
|
+
trailingIcon={<ChevronDownIcon width={14} height={14} />}
|
|
29
|
+
/>
|
|
30
|
+
}
|
|
31
|
+
>
|
|
32
|
+
<Flex gap='small' align='center' justify='start'>
|
|
33
|
+
{active?.label ?? 'Version'}
|
|
34
|
+
{active?.badge ? (
|
|
35
|
+
<Badge variant={active.badge.variant} size='micro'>
|
|
36
|
+
{active.badge.label}
|
|
37
|
+
</Badge>
|
|
38
|
+
) : null}
|
|
39
|
+
</Flex>
|
|
40
|
+
</Menu.Trigger>
|
|
41
|
+
<Menu.Content>
|
|
42
|
+
{versions.map(v => (
|
|
43
|
+
<Menu.Item
|
|
44
|
+
key={v.dir ?? '_latest'}
|
|
45
|
+
onClick={() => navigate(getVersionHomeHref(config, v.dir))}
|
|
46
|
+
>
|
|
47
|
+
<Flex gap='small' align='center'>
|
|
48
|
+
{v.label}
|
|
49
|
+
{v.badge ? (
|
|
50
|
+
<Badge variant={v.badge.variant} size='micro'>
|
|
51
|
+
{v.badge.label}
|
|
52
|
+
</Badge>
|
|
53
|
+
) : null}
|
|
54
|
+
</Flex>
|
|
55
|
+
</Menu.Item>
|
|
56
|
+
))}
|
|
57
|
+
</Menu.Content>
|
|
58
|
+
</Menu>
|
|
59
|
+
);
|
|
60
|
+
}
|