@raystack/chronicle 0.3.0 → 0.4.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.
Files changed (83) hide show
  1. package/dist/cli/index.js +289 -9931
  2. package/package.json +20 -12
  3. package/src/cli/commands/build.ts +28 -31
  4. package/src/cli/commands/dev.ts +24 -31
  5. package/src/cli/commands/init.ts +38 -132
  6. package/src/cli/commands/serve.ts +36 -55
  7. package/src/cli/commands/start.ts +20 -31
  8. package/src/cli/index.ts +14 -14
  9. package/src/cli/utils/config.ts +25 -26
  10. package/src/cli/utils/index.ts +3 -3
  11. package/src/cli/utils/resolve.ts +7 -3
  12. package/src/cli/utils/scaffold.ts +11 -130
  13. package/src/components/mdx/code.tsx +10 -1
  14. package/src/components/mdx/details.module.css +1 -26
  15. package/src/components/mdx/details.tsx +2 -3
  16. package/src/components/mdx/image.tsx +5 -34
  17. package/src/components/mdx/index.tsx +15 -1
  18. package/src/components/mdx/link.tsx +18 -15
  19. package/src/components/ui/breadcrumbs.tsx +8 -42
  20. package/src/components/ui/search.tsx +63 -51
  21. package/src/lib/api-routes.ts +6 -8
  22. package/src/lib/config.ts +12 -36
  23. package/src/lib/head.tsx +49 -0
  24. package/src/lib/openapi.ts +8 -8
  25. package/src/lib/page-context.tsx +111 -0
  26. package/src/lib/remark-strip-md-extensions.ts +14 -0
  27. package/src/lib/source.ts +139 -63
  28. package/src/pages/ApiLayout.tsx +33 -0
  29. package/src/pages/ApiPage.tsx +73 -0
  30. package/src/pages/DocsLayout.tsx +18 -0
  31. package/src/pages/DocsPage.tsx +43 -0
  32. package/src/pages/NotFound.tsx +17 -0
  33. package/src/server/App.tsx +72 -0
  34. package/src/server/api/apis-proxy.ts +69 -0
  35. package/src/server/api/health.ts +5 -0
  36. package/src/server/api/page/[...slug].ts +18 -0
  37. package/src/server/api/search.ts +118 -0
  38. package/src/server/api/specs.ts +9 -0
  39. package/src/server/build-search-index.ts +117 -0
  40. package/src/server/entry-client.tsx +88 -0
  41. package/src/server/entry-server.tsx +102 -0
  42. package/src/server/routes/llms.txt.ts +21 -0
  43. package/src/server/routes/og.tsx +75 -0
  44. package/src/server/routes/robots.txt.ts +11 -0
  45. package/src/server/routes/sitemap.xml.ts +40 -0
  46. package/src/server/utils/safe-path.ts +17 -0
  47. package/src/server/vite-config.ts +129 -0
  48. package/src/themes/default/Layout.tsx +78 -48
  49. package/src/themes/default/Page.module.css +44 -0
  50. package/src/themes/default/Page.tsx +9 -11
  51. package/src/themes/default/Toc.tsx +25 -39
  52. package/src/themes/default/index.ts +7 -9
  53. package/src/themes/paper/ChapterNav.tsx +64 -45
  54. package/src/themes/paper/Layout.module.css +1 -1
  55. package/src/themes/paper/Layout.tsx +24 -12
  56. package/src/themes/paper/Page.module.css +16 -4
  57. package/src/themes/paper/Page.tsx +56 -63
  58. package/src/themes/paper/ReadingProgress.tsx +160 -139
  59. package/src/themes/paper/index.ts +5 -5
  60. package/src/themes/registry.ts +14 -7
  61. package/src/types/content.ts +5 -21
  62. package/src/types/globals.d.ts +4 -0
  63. package/src/types/theme.ts +4 -3
  64. package/tsconfig.json +2 -3
  65. package/next.config.mjs +0 -10
  66. package/source.config.ts +0 -51
  67. package/src/app/[[...slug]]/layout.tsx +0 -15
  68. package/src/app/[[...slug]]/page.tsx +0 -106
  69. package/src/app/api/apis-proxy/route.ts +0 -59
  70. package/src/app/api/health/route.ts +0 -3
  71. package/src/app/api/search/route.ts +0 -90
  72. package/src/app/apis/[[...slug]]/layout.tsx +0 -26
  73. package/src/app/apis/[[...slug]]/page.tsx +0 -117
  74. package/src/app/layout.tsx +0 -57
  75. package/src/app/llms-full.txt/route.ts +0 -18
  76. package/src/app/llms.txt/route.ts +0 -15
  77. package/src/app/og/route.tsx +0 -62
  78. package/src/app/providers.tsx +0 -8
  79. package/src/app/robots.ts +0 -10
  80. package/src/app/sitemap.ts +0 -29
  81. package/src/cli/utils/process.ts +0 -7
  82. package/src/themes/default/font.ts +0 -6
  83. /package/src/{app/apis/[[...slug]]/layout.module.css → pages/ApiLayout.module.css} +0 -0
@@ -1,64 +1,86 @@
1
- "use client";
2
-
3
- import { useMemo, useEffect, useRef } from "react";
4
- import { usePathname } from "next/navigation";
5
- import NextLink from "next/link";
6
- import { cx } from "class-variance-authority";
7
- import { Flex, Navbar, Headline, Link, Sidebar, Button } from "@raystack/apsara";
8
- import { RectangleStackIcon } from "@heroicons/react/24/outline";
9
- import { ClientThemeSwitcher } from "@/components/ui/client-theme-switcher";
10
- import { Search } from "@/components/ui/search";
11
- import { Footer } from "@/components/ui/footer";
12
- import { MethodBadge } from "@/components/api/method-badge";
13
- import type { ThemeLayoutProps, PageTreeItem } from "@/types";
14
- import styles from "./Layout.module.css";
1
+ import { RectangleStackIcon } from '@heroicons/react/24/outline';
2
+ import {
3
+ Button,
4
+ Flex,
5
+ Headline,
6
+ Link,
7
+ Navbar,
8
+ Sidebar
9
+ } from '@raystack/apsara';
10
+ import { cx } from 'class-variance-authority';
11
+ import { useEffect, useRef } from 'react';
12
+ import { Link as RouterLink, useLocation } from 'react-router';
13
+ import { MethodBadge } from '@/components/api/method-badge';
14
+ import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher';
15
+ import { Footer } from '@/components/ui/footer';
16
+ import { Search } from '@/components/ui/search';
17
+ import type { Node } from 'fumadocs-core/page-tree';
18
+ import type { ThemeLayoutProps } from '@/types';
19
+ import styles from './Layout.module.css';
15
20
 
16
21
  const iconMap: Record<string, React.ReactNode> = {
17
- "rectangle-stack": <RectangleStackIcon width={16} height={16} />,
18
- "method-get": <MethodBadge method="GET" size="micro" />,
19
- "method-post": <MethodBadge method="POST" size="micro" />,
20
- "method-put": <MethodBadge method="PUT" size="micro" />,
21
- "method-delete": <MethodBadge method="DELETE" size="micro" />,
22
- "method-patch": <MethodBadge method="PATCH" size="micro" />,
22
+ 'rectangle-stack': <RectangleStackIcon width={16} height={16} />,
23
+ 'method-get': <MethodBadge method='GET' size='micro' />,
24
+ 'method-post': <MethodBadge method='POST' size='micro' />,
25
+ 'method-put': <MethodBadge method='PUT' size='micro' />,
26
+ 'method-delete': <MethodBadge method='DELETE' size='micro' />,
27
+ 'method-patch': <MethodBadge method='PATCH' size='micro' />
23
28
  };
24
29
 
25
30
  let savedScrollTop = 0;
26
31
 
27
- export function Layout({ children, config, tree, classNames }: ThemeLayoutProps) {
28
- const pathname = usePathname();
32
+ export function Layout({
33
+ children,
34
+ config,
35
+ tree,
36
+ classNames
37
+ }: ThemeLayoutProps) {
38
+ const { pathname } = useLocation();
29
39
  const scrollRef = useRef<HTMLDivElement>(null);
30
40
 
31
41
  useEffect(() => {
32
42
  const el = scrollRef.current;
33
43
  if (!el) return;
34
- const onScroll = () => { savedScrollTop = el.scrollTop; };
44
+ const onScroll = () => {
45
+ savedScrollTop = el.scrollTop;
46
+ };
35
47
  el.addEventListener('scroll', onScroll);
36
48
  return () => el.removeEventListener('scroll', onScroll);
37
49
  }, []);
38
50
 
39
51
  useEffect(() => {
40
52
  const el = scrollRef.current;
41
- if (el) requestAnimationFrame(() => { el.scrollTop = savedScrollTop; });
53
+ if (el)
54
+ requestAnimationFrame(() => {
55
+ el.scrollTop = savedScrollTop;
56
+ });
42
57
  }, [pathname]);
43
58
 
44
59
  return (
45
- <Flex direction="column" className={cx(styles.layout, classNames?.layout)}>
60
+ <Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
46
61
  <Navbar className={styles.header}>
47
62
  <Navbar.Start>
48
- <NextLink href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
49
- <Headline size="small" weight="medium" as="h1">
63
+ <RouterLink
64
+ to='/'
65
+ style={{ textDecoration: 'none', color: 'inherit' }}
66
+ >
67
+ <Headline size='small' weight='medium' as='h1'>
50
68
  {config.title}
51
69
  </Headline>
52
- </NextLink>
70
+ </RouterLink>
53
71
  </Navbar.Start>
54
72
  <Navbar.End>
55
- <Flex gap="medium" align="center" className={styles.navActions}>
56
- {config.api?.map((api) => (
57
- <NextLink key={api.basePath} href={api.basePath} className={styles.navButton}>
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
+ >
58
80
  {api.name} API
59
- </NextLink>
81
+ </RouterLink>
60
82
  ))}
61
- {config.navigation?.links?.map((link) => (
83
+ {config.navigation?.links?.map(link => (
62
84
  <Link key={link.href} href={link.href}>
63
85
  {link.label}
64
86
  </Link>
@@ -69,18 +91,24 @@ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps)
69
91
  </Navbar.End>
70
92
  </Navbar>
71
93
  <Flex className={cx(styles.body, classNames?.body)}>
72
- <Sidebar defaultOpen collapsible={false} className={cx(styles.sidebar, classNames?.sidebar)}>
94
+ <Sidebar
95
+ defaultOpen
96
+ collapsible={false}
97
+ className={cx(styles.sidebar, classNames?.sidebar)}
98
+ >
73
99
  <Sidebar.Main ref={scrollRef}>
74
- {tree.children.map((item) => (
100
+ {tree.children.map((item, i) => (
75
101
  <SidebarNode
76
- key={item.url ?? item.name}
102
+ key={item.type === 'page' ? item.url : (item.name?.toString() ?? i)}
77
103
  item={item}
78
104
  pathname={pathname}
79
105
  />
80
106
  ))}
81
107
  </Sidebar.Main>
82
108
  </Sidebar>
83
- <main className={cx(styles.content, classNames?.content)}>{children}</main>
109
+ <main className={cx(styles.content, classNames?.content)}>
110
+ {children}
111
+ </main>
84
112
  </Flex>
85
113
  <Footer config={config.footer} />
86
114
  </Flex>
@@ -89,25 +117,26 @@ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps)
89
117
 
90
118
  function SidebarNode({
91
119
  item,
92
- pathname,
120
+ pathname
93
121
  }: {
94
- item: PageTreeItem;
122
+ item: Node;
95
123
  pathname: string;
96
124
  }) {
97
- if (item.type === "separator") {
125
+ if (item.type === 'separator') {
98
126
  return null;
99
127
  }
100
128
 
101
- if (item.type === "folder" && item.children) {
129
+ if (item.type === 'folder') {
130
+ const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
102
131
  return (
103
132
  <Sidebar.Group
104
- label={item.name}
105
- leadingIcon={item.icon ? iconMap[item.icon] : undefined}
133
+ label={item.name?.toString() ?? ''}
134
+ leadingIcon={icon ?? undefined}
106
135
  classNames={{ items: styles.groupItems }}
107
136
  >
108
- {item.children.map((child) => (
137
+ {item.children.map((child, i) => (
109
138
  <SidebarNode
110
- key={child.url ?? child.name}
139
+ key={child.type === 'page' ? child.url : (child.name?.toString() ?? i)}
111
140
  item={child}
112
141
  pathname={pathname}
113
142
  />
@@ -117,14 +146,15 @@ function SidebarNode({
117
146
  }
118
147
 
119
148
  const isActive = pathname === item.url;
120
- const href = item.url ?? "#";
121
- const link = useMemo(() => <NextLink href={href} scroll={false} />, [href]);
149
+ const href = item.url ?? '#';
150
+ const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
151
+ const link = <RouterLink to={href} />;
122
152
 
123
153
  return (
124
154
  <Sidebar.Item
125
155
  href={href}
126
156
  active={isActive}
127
- leadingIcon={item.icon ? iconMap[item.icon] : undefined}
157
+ leadingIcon={icon ?? undefined}
128
158
  as={link}
129
159
  >
130
160
  {item.name}
@@ -38,9 +38,53 @@
38
38
  margin-bottom: var(--rs-space-3);
39
39
  }
40
40
 
41
+ .content img {
42
+ max-width: 100%;
43
+ height: auto;
44
+ }
45
+
41
46
  .content table {
42
47
  display: block;
43
48
  max-width: 100%;
44
49
  overflow-x: auto;
45
50
  margin-bottom: var(--rs-space-5);
46
51
  }
52
+
53
+ .content details {
54
+ border: 1px solid var(--rs-color-border-base-primary);
55
+ border-radius: var(--rs-radius-2);
56
+ margin: var(--rs-space-5) 0;
57
+ overflow: hidden;
58
+ }
59
+
60
+ .content details summary {
61
+ padding: var(--rs-space-4) var(--rs-space-5);
62
+ cursor: pointer;
63
+ font-weight: 500;
64
+ font-size: var(--rs-font-size-small);
65
+ color: var(--rs-color-text-base-primary);
66
+ background: var(--rs-color-background-base-secondary);
67
+ list-style: none;
68
+ display: flex;
69
+ align-items: center;
70
+ gap: var(--rs-space-3);
71
+ }
72
+
73
+ .content details summary::-webkit-details-marker {
74
+ display: none;
75
+ }
76
+
77
+ .content details summary::before {
78
+ content: '▶';
79
+ font-size: 10px;
80
+ transition: transform 0.2s ease;
81
+ color: var(--rs-color-text-base-secondary);
82
+ }
83
+
84
+ .content details[open] > summary::before {
85
+ transform: rotate(90deg);
86
+ }
87
+
88
+ .content details > :not(summary) {
89
+ padding: var(--rs-space-4) var(--rs-space-5);
90
+ }
@@ -1,21 +1,19 @@
1
- 'use client'
1
+ 'use client';
2
2
 
3
- import { Flex } from '@raystack/apsara'
4
- import type { ThemePageProps } from '@/types'
5
- import { Breadcrumbs } from '@/components/ui/breadcrumbs'
6
- import { Toc } from './Toc'
7
- import styles from './Page.module.css'
3
+ import { Flex } from '@raystack/apsara';
4
+ import { Breadcrumbs } from '@/components/ui/breadcrumbs';
5
+ import type { ThemePageProps } from '@/types';
6
+ import styles from './Page.module.css';
7
+ import { Toc } from './Toc';
8
8
 
9
9
  export function Page({ page, tree }: ThemePageProps) {
10
10
  return (
11
11
  <Flex className={styles.page}>
12
- <article className={styles.article}>
12
+ <article className={styles.article} data-article-content>
13
13
  <Breadcrumbs slug={page.slug} tree={tree} />
14
- <div className={styles.content}>
15
- {page.content}
16
- </div>
14
+ <div className={styles.content}>{page.content}</div>
17
15
  </article>
18
16
  <Toc items={page.toc} />
19
17
  </Flex>
20
- )
18
+ );
21
19
  }
@@ -1,55 +1,41 @@
1
- 'use client'
1
+ 'use client';
2
2
 
3
- import { useEffect, useState } from 'react'
4
- import { Text } from '@raystack/apsara'
5
- import type { TocItem } from '@/types'
6
- import styles from './Toc.module.css'
3
+ import { Text } from '@raystack/apsara';
4
+ import { AnchorProvider, useActiveAnchor } from 'fumadocs-core/toc';
5
+ import type { TableOfContents, TOCItemType } from 'fumadocs-core/toc';
6
+ import styles from './Toc.module.css';
7
7
 
8
8
  interface TocProps {
9
- items: TocItem[]
9
+ items: TableOfContents;
10
10
  }
11
11
 
12
12
  export function Toc({ items }: TocProps) {
13
- const [activeId, setActiveId] = useState<string>('')
13
+ const filteredItems = items.filter(
14
+ item => item.depth >= 2 && item.depth <= 3
15
+ );
14
16
 
15
- // Filter to only show h2 and h3 headings
16
- const filteredItems = items.filter((item) => item.depth >= 2 && item.depth <= 3)
17
+ if (filteredItems.length === 0) return null;
17
18
 
18
- useEffect(() => {
19
- const headingIds = filteredItems.map((item) => item.url.replace('#', ''))
20
-
21
- const observer = new IntersectionObserver(
22
- (entries) => {
23
- entries.forEach((entry) => {
24
- if (entry.isIntersecting) {
25
- setActiveId(entry.target.id)
26
- }
27
- })
28
- },
29
- // -80px top: offset for fixed header, -80% bottom: trigger when heading is in top 20% of viewport
30
- { rootMargin: '-80px 0px -80% 0px' }
31
- )
32
-
33
- headingIds.forEach((id) => {
34
- const element = document.getElementById(id)
35
- if (element) observer.observe(element)
36
- })
37
-
38
- return () => observer.disconnect()
39
- }, [filteredItems])
19
+ return (
20
+ <AnchorProvider toc={filteredItems} single>
21
+ <TocContent items={filteredItems} />
22
+ </AnchorProvider>
23
+ );
24
+ }
40
25
 
41
- if (filteredItems.length === 0) return null
26
+ function TocContent({ items }: { items: TOCItemType[] }) {
27
+ const activeAnchor = useActiveAnchor();
42
28
 
43
29
  return (
44
30
  <aside className={styles.toc}>
45
- <Text size={1} weight="medium" className={styles.title}>
31
+ <Text size={1} weight='medium' className={styles.title}>
46
32
  On this page
47
33
  </Text>
48
34
  <nav className={styles.nav}>
49
- {filteredItems.map((item) => {
50
- const id = item.url.replace('#', '')
51
- const isActive = activeId === id
52
- const isNested = item.depth > 2
35
+ {items.map(item => {
36
+ const id = item.url.replace('#', '');
37
+ const isActive = activeAnchor === id;
38
+ const isNested = item.depth > 2;
53
39
  return (
54
40
  <a
55
41
  key={item.url}
@@ -58,9 +44,9 @@ export function Toc({ items }: TocProps) {
58
44
  >
59
45
  {item.title}
60
46
  </a>
61
- )
47
+ );
62
48
  })}
63
49
  </nav>
64
50
  </aside>
65
- )
51
+ );
66
52
  }
@@ -1,13 +1,11 @@
1
- import { Layout } from './Layout'
2
- import { Page } from './Page'
3
- import { Toc } from './Toc'
4
- import { inter } from './font'
5
- import type { Theme } from '@/types'
1
+ import type { Theme } from '@/types';
2
+ import { Layout } from './Layout';
3
+ import { Page } from './Page';
4
+ import { Toc } from './Toc';
6
5
 
7
6
  export const defaultTheme: Theme = {
8
7
  Layout,
9
- Page,
10
- className: inter.className,
11
- }
8
+ Page
9
+ };
12
10
 
13
- export { Layout, Page, Toc }
11
+ export { Layout, Page, Toc };
@@ -1,96 +1,115 @@
1
- 'use client'
2
-
3
- import { usePathname } from 'next/navigation'
4
- import NextLink from 'next/link'
5
- import { MethodBadge } from '@/components/api/method-badge'
6
- import type { PageTree, PageTreeItem } from '@/types'
7
- import styles from './ChapterNav.module.css'
1
+ import { Link as RouterLink, useLocation } from 'react-router';
2
+ import { MethodBadge } from '@/components/api/method-badge';
3
+ import type { Root, Node } from 'fumadocs-core/page-tree';
4
+ import styles from './ChapterNav.module.css';
8
5
 
9
6
  const iconMap: Record<string, React.ReactNode> = {
10
- 'method-get': <MethodBadge method="GET" size="micro" />,
11
- 'method-post': <MethodBadge method="POST" size="micro" />,
12
- 'method-put': <MethodBadge method="PUT" size="micro" />,
13
- 'method-delete': <MethodBadge method="DELETE" size="micro" />,
14
- 'method-patch': <MethodBadge method="PATCH" size="micro" />,
15
- }
7
+ 'method-get': <MethodBadge method='GET' size='micro' />,
8
+ 'method-post': <MethodBadge method='POST' size='micro' />,
9
+ 'method-put': <MethodBadge method='PUT' size='micro' />,
10
+ 'method-delete': <MethodBadge method='DELETE' size='micro' />,
11
+ 'method-patch': <MethodBadge method='PATCH' size='micro' />
12
+ };
16
13
 
17
14
  interface ChapterNavProps {
18
- tree: PageTree
15
+ tree: Root;
19
16
  }
20
17
 
21
- function buildChapterIndices(children: PageTreeItem[]): Map<PageTreeItem, number> {
22
- const indices = new Map<PageTreeItem, number>()
23
- let index = 0
18
+ function buildChapterIndices(
19
+ children: Node[]
20
+ ): Map<Node, number> {
21
+ const indices = new Map<Node, number>();
22
+ let index = 0;
24
23
  for (const item of children) {
25
- if (item.type === 'folder' && item.children) {
26
- index++
27
- indices.set(item, index)
24
+ if (item.type === 'folder') {
25
+ index++;
26
+ indices.set(item, index);
28
27
  }
29
28
  }
30
- return indices
29
+ return indices;
31
30
  }
32
31
 
33
32
  export function ChapterNav({ tree }: ChapterNavProps) {
34
- const pathname = usePathname()
35
- const chapterIndices = buildChapterIndices(tree.children)
33
+ const { pathname } = useLocation();
34
+ const chapterIndices = buildChapterIndices(tree.children);
36
35
 
37
36
  return (
38
37
  <nav className={styles.nav}>
39
38
  <ul className={styles.chapterItems}>
40
- {tree.children.map((item) => {
41
- if (item.type === 'separator') return null
39
+ {tree.children.map(item => {
40
+ if (item.type === 'separator') return null;
42
41
 
43
- if (item.type === 'folder' && item.children) {
44
- const chapterIndex = chapterIndices.get(item) ?? 0
42
+ if (item.type === 'folder') {
43
+ const chapterIndex = chapterIndices.get(item) ?? 0;
45
44
  return (
46
- <li key={item.name} className={styles.chapter}>
45
+ <li key={item.name?.toString()} className={styles.chapter}>
47
46
  <span className={styles.chapterLabel}>
48
47
  {String(chapterIndex).padStart(2, '0')}. {item.name}
49
48
  </span>
50
49
  <ul className={styles.chapterItems}>
51
- {item.children.map((child) => (
52
- <ChapterItem key={child.url ?? child.name} item={child} pathname={pathname} />
50
+ {item.children.map(child => (
51
+ <ChapterItem
52
+ key={child.type === 'page' ? child.url : (child.name?.toString() ?? '')}
53
+ item={child}
54
+ pathname={pathname}
55
+ />
53
56
  ))}
54
57
  </ul>
55
58
  </li>
56
- )
59
+ );
57
60
  }
58
61
 
59
- return <ChapterItem key={item.url ?? item.name} item={item} pathname={pathname} />
62
+ return (
63
+ <ChapterItem
64
+ key={item.url ?? item.name?.toString() ?? ''}
65
+ item={item}
66
+ pathname={pathname}
67
+ />
68
+ );
60
69
  })}
61
70
  </ul>
62
71
  </nav>
63
- )
72
+ );
64
73
  }
65
74
 
66
- function ChapterItem({ item, pathname }: { item: PageTreeItem; pathname: string }) {
67
- if (item.type === 'separator') return null
75
+ function ChapterItem({
76
+ item,
77
+ pathname
78
+ }: {
79
+ item: Node;
80
+ pathname: string;
81
+ }) {
82
+ if (item.type === 'separator') return null;
68
83
 
69
- if (item.type === 'folder' && item.children) {
84
+ if (item.type === 'folder') {
70
85
  return (
71
86
  <li>
72
87
  <span className={styles.subLabel}>{item.name}</span>
73
88
  <ul className={styles.chapterItems}>
74
- {item.children.map((child) => (
75
- <ChapterItem key={child.url ?? child.name} item={child} pathname={pathname} />
89
+ {item.children.map(child => (
90
+ <ChapterItem
91
+ key={child.type === 'page' ? child.url : (child.name?.toString() ?? '')}
92
+ item={child}
93
+ pathname={pathname}
94
+ />
76
95
  ))}
77
96
  </ul>
78
97
  </li>
79
- )
98
+ );
80
99
  }
81
100
 
82
- const isActive = pathname === item.url
83
- const icon = item.icon ? iconMap[item.icon] : null
101
+ const isActive = pathname === item.url;
102
+ const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
84
103
 
85
104
  return (
86
105
  <li>
87
- <NextLink
88
- href={item.url ?? '#'}
106
+ <RouterLink
107
+ to={item.url ?? '#'}
89
108
  className={`${styles.link} ${isActive ? styles.active : ''}`}
90
109
  >
91
110
  {icon && <span className={styles.icon}>{icon}</span>}
92
111
  <span>{item.name}</span>
93
- </NextLink>
112
+ </RouterLink>
94
113
  </li>
95
- )
114
+ );
96
115
  }
@@ -13,7 +13,7 @@
13
13
  padding: var(--rs-space-7) var(--rs-space-5);
14
14
  background: var(--rs-color-background-neutral-primary);
15
15
  overflow-y: auto;
16
- font-family: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace;
16
+ font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
17
17
  }
18
18
 
19
19
  .title {
@@ -1,25 +1,37 @@
1
- 'use client'
1
+ 'use client';
2
2
 
3
- import { Flex, Headline } from '@raystack/apsara'
4
- import { cx } from 'class-variance-authority'
5
- import { Footer } from '@/components/ui/footer'
6
- import { ChapterNav } from './ChapterNav'
7
- import type { ThemeLayoutProps } from '@/types'
8
- import styles from './Layout.module.css'
3
+ import { Flex, Headline } from '@raystack/apsara';
4
+ import { cx } from 'class-variance-authority';
5
+ import { Footer } from '@/components/ui/footer';
6
+ import type { ThemeLayoutProps } from '@/types';
7
+ import { ChapterNav } from './ChapterNav';
8
+ import styles from './Layout.module.css';
9
9
 
10
- export function Layout({ children, config, tree, classNames }: ThemeLayoutProps) {
10
+ export function Layout({
11
+ children,
12
+ config,
13
+ tree,
14
+ classNames
15
+ }: ThemeLayoutProps) {
11
16
  return (
12
- <Flex direction="column" className={cx(styles.layout, classNames?.layout)}>
17
+ <Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
13
18
  <Flex className={cx(styles.body, classNames?.body)}>
14
19
  <aside className={cx(styles.sidebar, classNames?.sidebar)}>
15
- <Headline size="small" weight="medium" as="h1" className={styles.title}>
20
+ <Headline
21
+ size='small'
22
+ weight='medium'
23
+ as='h1'
24
+ className={styles.title}
25
+ >
16
26
  {config.title}
17
27
  </Headline>
18
28
  <ChapterNav tree={tree} />
19
29
  </aside>
20
- <div className={cx(styles.content, classNames?.content)}>{children}</div>
30
+ <div className={cx(styles.content, classNames?.content)}>
31
+ {children}
32
+ </div>
21
33
  </Flex>
22
34
  <Footer config={config.footer} />
23
35
  </Flex>
24
- )
36
+ );
25
37
  }