@raystack/chronicle 0.1.2

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 (101) hide show
  1. package/bin/chronicle.js +2 -0
  2. package/dist/cli/index.js +9763 -0
  3. package/next.config.mjs +10 -0
  4. package/package.json +60 -0
  5. package/source.config.ts +43 -0
  6. package/src/app/[[...slug]]/layout.tsx +15 -0
  7. package/src/app/[[...slug]]/page.tsx +57 -0
  8. package/src/app/api/apis-proxy/route.ts +59 -0
  9. package/src/app/api/health/route.ts +3 -0
  10. package/src/app/api/search/route.ts +90 -0
  11. package/src/app/apis/[[...slug]]/layout.module.css +22 -0
  12. package/src/app/apis/[[...slug]]/layout.tsx +26 -0
  13. package/src/app/apis/[[...slug]]/page.tsx +57 -0
  14. package/src/app/layout.tsx +26 -0
  15. package/src/app/providers.tsx +8 -0
  16. package/src/cli/commands/build.ts +31 -0
  17. package/src/cli/commands/dev.ts +32 -0
  18. package/src/cli/commands/init.ts +58 -0
  19. package/src/cli/commands/serve.ts +52 -0
  20. package/src/cli/commands/start.ts +32 -0
  21. package/src/cli/index.ts +21 -0
  22. package/src/cli/utils/config.ts +35 -0
  23. package/src/cli/utils/index.ts +2 -0
  24. package/src/cli/utils/process.ts +7 -0
  25. package/src/components/api/code-snippets.module.css +7 -0
  26. package/src/components/api/code-snippets.tsx +76 -0
  27. package/src/components/api/endpoint-page.module.css +58 -0
  28. package/src/components/api/endpoint-page.tsx +283 -0
  29. package/src/components/api/field-row.module.css +126 -0
  30. package/src/components/api/field-row.tsx +204 -0
  31. package/src/components/api/field-section.module.css +24 -0
  32. package/src/components/api/field-section.tsx +100 -0
  33. package/src/components/api/index.ts +8 -0
  34. package/src/components/api/json-editor.module.css +9 -0
  35. package/src/components/api/json-editor.tsx +61 -0
  36. package/src/components/api/key-value-editor.module.css +13 -0
  37. package/src/components/api/key-value-editor.tsx +62 -0
  38. package/src/components/api/method-badge.module.css +4 -0
  39. package/src/components/api/method-badge.tsx +29 -0
  40. package/src/components/api/response-panel.module.css +8 -0
  41. package/src/components/api/response-panel.tsx +44 -0
  42. package/src/components/common/breadcrumb.tsx +3 -0
  43. package/src/components/common/button.tsx +3 -0
  44. package/src/components/common/callout.module.css +7 -0
  45. package/src/components/common/callout.tsx +27 -0
  46. package/src/components/common/code-block.tsx +3 -0
  47. package/src/components/common/dialog.tsx +3 -0
  48. package/src/components/common/index.ts +10 -0
  49. package/src/components/common/input-field.tsx +3 -0
  50. package/src/components/common/sidebar.tsx +3 -0
  51. package/src/components/common/switch.tsx +3 -0
  52. package/src/components/common/table.tsx +3 -0
  53. package/src/components/common/tabs.tsx +3 -0
  54. package/src/components/mdx/code.module.css +42 -0
  55. package/src/components/mdx/code.tsx +27 -0
  56. package/src/components/mdx/details.module.css +37 -0
  57. package/src/components/mdx/details.tsx +18 -0
  58. package/src/components/mdx/image.tsx +38 -0
  59. package/src/components/mdx/index.tsx +35 -0
  60. package/src/components/mdx/link.tsx +38 -0
  61. package/src/components/mdx/mermaid.module.css +9 -0
  62. package/src/components/mdx/mermaid.tsx +37 -0
  63. package/src/components/mdx/paragraph.module.css +8 -0
  64. package/src/components/mdx/paragraph.tsx +19 -0
  65. package/src/components/mdx/table.tsx +40 -0
  66. package/src/components/ui/breadcrumbs.tsx +72 -0
  67. package/src/components/ui/client-theme-switcher.tsx +18 -0
  68. package/src/components/ui/footer.module.css +27 -0
  69. package/src/components/ui/footer.tsx +30 -0
  70. package/src/components/ui/search.module.css +104 -0
  71. package/src/components/ui/search.tsx +202 -0
  72. package/src/lib/api-routes.ts +120 -0
  73. package/src/lib/config.ts +36 -0
  74. package/src/lib/index.ts +2 -0
  75. package/src/lib/openapi.ts +188 -0
  76. package/src/lib/remark-unused-directives.ts +31 -0
  77. package/src/lib/schema.ts +99 -0
  78. package/src/lib/snippet-generators.ts +87 -0
  79. package/src/lib/source.ts +66 -0
  80. package/src/themes/default/Layout.module.css +81 -0
  81. package/src/themes/default/Layout.tsx +133 -0
  82. package/src/themes/default/Page.module.css +46 -0
  83. package/src/themes/default/Page.tsx +21 -0
  84. package/src/themes/default/Toc.module.css +48 -0
  85. package/src/themes/default/Toc.tsx +66 -0
  86. package/src/themes/default/font.ts +6 -0
  87. package/src/themes/default/index.ts +13 -0
  88. package/src/themes/paper/ChapterNav.module.css +71 -0
  89. package/src/themes/paper/ChapterNav.tsx +96 -0
  90. package/src/themes/paper/Layout.module.css +33 -0
  91. package/src/themes/paper/Layout.tsx +25 -0
  92. package/src/themes/paper/Page.module.css +174 -0
  93. package/src/themes/paper/Page.tsx +107 -0
  94. package/src/themes/paper/ReadingProgress.module.css +132 -0
  95. package/src/themes/paper/ReadingProgress.tsx +294 -0
  96. package/src/themes/paper/index.ts +8 -0
  97. package/src/themes/registry.ts +14 -0
  98. package/src/types/config.ts +64 -0
  99. package/src/types/content.ts +35 -0
  100. package/src/types/index.ts +3 -0
  101. package/src/types/theme.ts +22 -0
@@ -0,0 +1,87 @@
1
+ interface SnippetOptions {
2
+ method: string
3
+ url: string
4
+ headers: Record<string, string>
5
+ body?: string
6
+ }
7
+
8
+ export function generateCurl({ method, url, headers, body }: SnippetOptions): string {
9
+ const parts = [`curl -X ${method} '${url}'`]
10
+ for (const [key, value] of Object.entries(headers)) {
11
+ parts.push(` -H '${key}: ${value}'`)
12
+ }
13
+ if (body) {
14
+ parts.push(` -d '${body}'`)
15
+ }
16
+ return parts.join(' \\\n')
17
+ }
18
+
19
+ export function generatePython({ method, url, headers, body }: SnippetOptions): string {
20
+ const lines: string[] = ['import requests', '']
21
+ const methodLower = method.toLowerCase()
22
+ const headerEntries = Object.entries(headers)
23
+
24
+ lines.push(`response = requests.${methodLower}(`)
25
+ lines.push(` "${url}",`)
26
+
27
+ if (headerEntries.length > 0) {
28
+ lines.push(' headers={')
29
+ for (const [key, value] of headerEntries) {
30
+ lines.push(` "${key}": "${value}",`)
31
+ }
32
+ lines.push(' },')
33
+ }
34
+
35
+ if (body) {
36
+ lines.push(` json=${body},`)
37
+ }
38
+
39
+ lines.push(')')
40
+ lines.push('print(response.json())')
41
+ return lines.join('\n')
42
+ }
43
+
44
+ export function generateGo({ method, url, headers, body }: SnippetOptions): string {
45
+ const lines: string[] = []
46
+
47
+ if (body) {
48
+ lines.push('payload := strings.NewReader(`' + body + '`)')
49
+ lines.push('')
50
+ lines.push(`req, _ := http.NewRequest("${method}", "${url}", payload)`)
51
+ } else {
52
+ lines.push(`req, _ := http.NewRequest("${method}", "${url}", nil)`)
53
+ }
54
+
55
+ for (const [key, value] of Object.entries(headers)) {
56
+ lines.push(`req.Header.Set("${key}", "${value}")`)
57
+ }
58
+
59
+ lines.push('')
60
+ lines.push('resp, _ := http.DefaultClient.Do(req)')
61
+ lines.push('defer resp.Body.Close()')
62
+ return lines.join('\n')
63
+ }
64
+
65
+ export function generateTypeScript({ method, url, headers, body }: SnippetOptions): string {
66
+ const lines: string[] = []
67
+ const headerEntries = Object.entries(headers)
68
+
69
+ lines.push(`const response = await fetch("${url}", {`)
70
+ lines.push(` method: "${method}",`)
71
+
72
+ if (headerEntries.length > 0) {
73
+ lines.push(' headers: {')
74
+ for (const [key, value] of headerEntries) {
75
+ lines.push(` "${key}": "${value}",`)
76
+ }
77
+ lines.push(' },')
78
+ }
79
+
80
+ if (body) {
81
+ lines.push(` body: JSON.stringify(${body}),`)
82
+ }
83
+
84
+ lines.push('});')
85
+ lines.push('const data = await response.json();')
86
+ return lines.join('\n')
87
+ }
@@ -0,0 +1,66 @@
1
+ import { docs } from '../../.source/server'
2
+ import { loader } from 'fumadocs-core/source'
3
+ import type { PageTree, PageTreeItem, Frontmatter } from '@/types'
4
+
5
+ export const source = loader({
6
+ baseUrl: '/',
7
+ source: docs.toFumadocsSource(),
8
+ })
9
+
10
+ export function sortByOrder<T extends { frontmatter?: Frontmatter }>(
11
+ items: T[]
12
+ ): T[] {
13
+ return [...items].sort((a, b) => {
14
+ const orderA = a.frontmatter?.order ?? Number.MAX_SAFE_INTEGER
15
+ const orderB = b.frontmatter?.order ?? Number.MAX_SAFE_INTEGER
16
+ return orderA - orderB
17
+ })
18
+ }
19
+
20
+ export function buildPageTree(): PageTree {
21
+ const pages = source.getPages()
22
+ const folders = new Map<string, PageTreeItem[]>()
23
+ const rootPages: PageTreeItem[] = []
24
+
25
+ pages.forEach((page) => {
26
+ const data = page.data as { title?: string; order?: number }
27
+ const isIndex = page.url === '/'
28
+ const item: PageTreeItem = {
29
+ type: 'page',
30
+ name: data.title ?? page.slugs.join('/') ?? 'Untitled',
31
+ url: page.url,
32
+ order: data.order ?? (isIndex ? 0 : undefined),
33
+ }
34
+
35
+ if (page.slugs.length > 1) {
36
+ const folder = page.slugs[0]
37
+ if (!folders.has(folder)) {
38
+ folders.set(folder, [])
39
+ }
40
+ folders.get(folder)?.push(item)
41
+ } else {
42
+ rootPages.push(item)
43
+ }
44
+ })
45
+
46
+ const sortByOrder = (items: PageTreeItem[]) =>
47
+ items.sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER))
48
+
49
+ const children: PageTreeItem[] = sortByOrder(rootPages)
50
+
51
+ const folderItems: PageTreeItem[] = []
52
+ folders.forEach((items, folder) => {
53
+ const sorted = sortByOrder(items)
54
+ const indexPage = sorted[0]
55
+ folderItems.push({
56
+ type: 'folder',
57
+ name: folder.charAt(0).toUpperCase() + folder.slice(1),
58
+ order: indexPage?.order,
59
+ children: sorted,
60
+ })
61
+ })
62
+
63
+ children.push(...sortByOrder(folderItems))
64
+
65
+ return { name: 'root', children }
66
+ }
@@ -0,0 +1,81 @@
1
+ .layout {
2
+ min-height: 100vh;
3
+ }
4
+
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
+ .body {
14
+ flex: 1;
15
+ }
16
+
17
+ .sidebar {
18
+ width: 260px;
19
+ position: sticky;
20
+ top: 0;
21
+ height: 100vh;
22
+ }
23
+
24
+ .content {
25
+ flex: 1;
26
+ padding: var(--rs-space-9);
27
+ }
28
+
29
+ .sidebarList {
30
+ list-style: none;
31
+ padding: 0;
32
+ margin: 0;
33
+ }
34
+
35
+ .separator {
36
+ height: 1px;
37
+ background: var(--rs-color-border-base-primary);
38
+ margin: var(--rs-space-3) 0;
39
+ }
40
+
41
+ .folder {
42
+ margin-bottom: var(--rs-space-3);
43
+ }
44
+
45
+ .folderLabel {
46
+ font-weight: 500;
47
+ font-size: 0.875rem;
48
+ color: var(--rs-color-text-base-secondary);
49
+ text-transform: uppercase;
50
+ letter-spacing: 0.05em;
51
+ }
52
+
53
+ .folder > .sidebarList {
54
+ margin-top: var(--rs-space-2);
55
+ padding-left: var(--rs-space-4);
56
+ }
57
+
58
+ .navButton {
59
+ display: flex;
60
+ align-items: center;
61
+ height: 32px;
62
+ padding: 0 var(--rs-space-4);
63
+ border: 1px solid var(--rs-color-border-base-primary);
64
+ border-radius: var(--rs-radius-2);
65
+ font-size: var(--rs-font-size-small);
66
+ font-weight: var(--rs-font-weight-medium);
67
+ color: var(--rs-color-foreground-base-primary);
68
+ text-decoration: none;
69
+ }
70
+
71
+ .navButton:hover {
72
+ background: var(--rs-color-background-base-primary-hover);
73
+ }
74
+
75
+ .groupItems {
76
+ padding-left: var(--rs-space-4);
77
+ }
78
+
79
+ .page {
80
+ padding: var(--rs-space-2) 0;
81
+ }
@@ -0,0 +1,133 @@
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";
15
+
16
+ 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" />,
23
+ };
24
+
25
+ let savedScrollTop = 0;
26
+
27
+ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps) {
28
+ const pathname = usePathname();
29
+ const scrollRef = useRef<HTMLDivElement>(null);
30
+
31
+ useEffect(() => {
32
+ const el = scrollRef.current;
33
+ if (!el) return;
34
+ const onScroll = () => { savedScrollTop = el.scrollTop; };
35
+ el.addEventListener('scroll', onScroll);
36
+ return () => el.removeEventListener('scroll', onScroll);
37
+ }, []);
38
+
39
+ useEffect(() => {
40
+ const el = scrollRef.current;
41
+ if (el) requestAnimationFrame(() => { el.scrollTop = savedScrollTop; });
42
+ }, [pathname]);
43
+
44
+ return (
45
+ <Flex direction="column" className={cx(styles.layout, classNames?.layout)}>
46
+ <Navbar className={styles.header}>
47
+ <Navbar.Start>
48
+ <NextLink href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
49
+ <Headline size="small" weight="medium" as="h1">
50
+ {config.title}
51
+ </Headline>
52
+ </NextLink>
53
+ </Navbar.Start>
54
+ <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}>
58
+ {api.name} API
59
+ </NextLink>
60
+ ))}
61
+ {config.navigation?.links?.map((link) => (
62
+ <Link key={link.href} href={link.href}>
63
+ {link.label}
64
+ </Link>
65
+ ))}
66
+ {config.search?.enabled && <Search />}
67
+ </Flex>
68
+ <ClientThemeSwitcher size={16} />
69
+ </Navbar.End>
70
+ </Navbar>
71
+ <Flex className={cx(styles.body, classNames?.body)}>
72
+ <Sidebar defaultOpen collapsible={false} className={cx(styles.sidebar, classNames?.sidebar)}>
73
+ <Sidebar.Main ref={scrollRef}>
74
+ {tree.children.map((item) => (
75
+ <SidebarNode
76
+ key={item.url ?? item.name}
77
+ item={item}
78
+ pathname={pathname}
79
+ />
80
+ ))}
81
+ </Sidebar.Main>
82
+ </Sidebar>
83
+ <main className={cx(styles.content, classNames?.content)}>{children}</main>
84
+ </Flex>
85
+ <Footer config={config.footer} />
86
+ </Flex>
87
+ );
88
+ }
89
+
90
+ function SidebarNode({
91
+ item,
92
+ pathname,
93
+ }: {
94
+ item: PageTreeItem;
95
+ pathname: string;
96
+ }) {
97
+ if (item.type === "separator") {
98
+ return null;
99
+ }
100
+
101
+ if (item.type === "folder" && item.children) {
102
+ return (
103
+ <Sidebar.Group
104
+ label={item.name}
105
+ leadingIcon={item.icon ? iconMap[item.icon] : undefined}
106
+ classNames={{ items: styles.groupItems }}
107
+ >
108
+ {item.children.map((child) => (
109
+ <SidebarNode
110
+ key={child.url ?? child.name}
111
+ item={child}
112
+ pathname={pathname}
113
+ />
114
+ ))}
115
+ </Sidebar.Group>
116
+ );
117
+ }
118
+
119
+ const isActive = pathname === item.url;
120
+ const href = item.url ?? "#";
121
+ const link = useMemo(() => <NextLink href={href} scroll={false} />, [href]);
122
+
123
+ return (
124
+ <Sidebar.Item
125
+ href={href}
126
+ active={isActive}
127
+ leadingIcon={item.icon ? iconMap[item.icon] : undefined}
128
+ as={link}
129
+ >
130
+ {item.name}
131
+ </Sidebar.Item>
132
+ );
133
+ }
@@ -0,0 +1,46 @@
1
+ .page {
2
+ gap: var(--rs-space-9);
3
+ }
4
+
5
+ .article {
6
+ flex: 1;
7
+ min-width: 0;
8
+ max-width: 768px;
9
+ }
10
+
11
+ .content {
12
+ line-height: 1.7;
13
+ }
14
+
15
+ .content h1,
16
+ .content h2,
17
+ .content h3,
18
+ .content h4,
19
+ .content h5,
20
+ .content h6 {
21
+ margin-top: var(--rs-space-8);
22
+ margin-bottom: var(--rs-space-5);
23
+ line-height: 1.4;
24
+ }
25
+
26
+ .content ul,
27
+ .content ol {
28
+ padding-left: var(--rs-space-5);
29
+ margin-bottom: var(--rs-space-5);
30
+ }
31
+
32
+ .content li {
33
+ font-size: var(--rs-font-size-regular);
34
+ margin: var(--rs-space-2) 0;
35
+ }
36
+
37
+ .content [role="tablist"] {
38
+ margin-bottom: var(--rs-space-3);
39
+ }
40
+
41
+ .content table {
42
+ display: block;
43
+ max-width: 100%;
44
+ overflow-x: auto;
45
+ margin-bottom: var(--rs-space-5);
46
+ }
@@ -0,0 +1,21 @@
1
+ 'use client'
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'
8
+
9
+ export function Page({ page, tree }: ThemePageProps) {
10
+ return (
11
+ <Flex className={styles.page}>
12
+ <article className={styles.article}>
13
+ <Breadcrumbs slug={page.slug} tree={tree} />
14
+ <div className={styles.content}>
15
+ {page.content}
16
+ </div>
17
+ </article>
18
+ <Toc items={page.toc} />
19
+ </Flex>
20
+ )
21
+ }
@@ -0,0 +1,48 @@
1
+ .toc {
2
+ width: 200px;
3
+ flex-shrink: 0;
4
+ position: sticky;
5
+ top: var(--rs-space-9);
6
+ max-height: calc(100vh - var(--rs-space-17));
7
+ overflow-y: auto;
8
+ }
9
+
10
+ .title {
11
+ display: block;
12
+ color: var(--rs-color-foreground-base-secondary);
13
+ text-transform: uppercase;
14
+ letter-spacing: 0.05em;
15
+ margin-bottom: var(--rs-space-3);
16
+ font-size: var(--rs-font-size-mini);
17
+ }
18
+
19
+ .nav {
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 0;
23
+ border-left: 1px solid var(--rs-color-border-base-primary);
24
+ padding-left: var(--rs-space-3);
25
+ margin-bottom: var(--rs-space-6);
26
+ }
27
+
28
+ .link {
29
+ color: var(--rs-color-foreground-base-tertiary);
30
+ text-decoration: none;
31
+ font-size: var(--rs-font-size-small);
32
+ line-height: 1.4;
33
+ padding: var(--rs-space-1) 0;
34
+ transition: color 0.15s ease;
35
+ }
36
+
37
+ .link:hover {
38
+ color: var(--rs-color-foreground-base-primary);
39
+ }
40
+
41
+ .active {
42
+ color: var(--rs-color-foreground-base-primary);
43
+ font-weight: 500;
44
+ }
45
+
46
+ .nested {
47
+ padding-left: var(--rs-space-3);
48
+ }
@@ -0,0 +1,66 @@
1
+ 'use client'
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'
7
+
8
+ interface TocProps {
9
+ items: TocItem[]
10
+ }
11
+
12
+ export function Toc({ items }: TocProps) {
13
+ const [activeId, setActiveId] = useState<string>('')
14
+
15
+ // Filter to only show h2 and h3 headings
16
+ const filteredItems = items.filter((item) => item.depth >= 2 && item.depth <= 3)
17
+
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])
40
+
41
+ if (filteredItems.length === 0) return null
42
+
43
+ return (
44
+ <aside className={styles.toc}>
45
+ <Text size={1} weight="medium" className={styles.title}>
46
+ On this page
47
+ </Text>
48
+ <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
53
+ return (
54
+ <a
55
+ key={item.url}
56
+ href={item.url}
57
+ className={`${styles.link} ${isActive ? styles.active : ''} ${isNested ? styles.nested : ''}`}
58
+ >
59
+ {item.title}
60
+ </a>
61
+ )
62
+ })}
63
+ </nav>
64
+ </aside>
65
+ )
66
+ }
@@ -0,0 +1,6 @@
1
+ import { Inter } from 'next/font/google'
2
+
3
+ export const inter = Inter({
4
+ subsets: ['latin'],
5
+ display: 'swap',
6
+ })
@@ -0,0 +1,13 @@
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'
6
+
7
+ export const defaultTheme: Theme = {
8
+ Layout,
9
+ Page,
10
+ className: inter.className,
11
+ }
12
+
13
+ export { Layout, Page, Toc }
@@ -0,0 +1,71 @@
1
+ .nav {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--rs-space-5);
5
+ }
6
+
7
+ .chapter {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: var(--rs-space-2);
11
+ }
12
+
13
+ .chapterLabel {
14
+ font-size: var(--rs-font-size-small);
15
+ font-weight: 600;
16
+ text-transform: uppercase;
17
+ letter-spacing: 0.05em;
18
+ color: var(--rs-color-foreground-base-primary);
19
+ white-space: nowrap;
20
+ overflow: hidden;
21
+ text-overflow: ellipsis;
22
+ }
23
+
24
+ .chapterItems {
25
+ list-style: none;
26
+ padding: 0;
27
+ margin: 0;
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: var(--rs-space-1);
31
+ padding-left: var(--rs-space-4);
32
+ }
33
+
34
+ .link {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: var(--rs-space-2);
38
+ font-size: var(--rs-font-size-small);
39
+ color: var(--rs-color-foreground-base-tertiary);
40
+ text-decoration: none;
41
+ padding: var(--rs-space-1) 0;
42
+ white-space: nowrap;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ }
46
+
47
+ .link:hover {
48
+ color: var(--rs-color-foreground-base-primary);
49
+ }
50
+
51
+ .active {
52
+ color: var(--rs-color-foreground-accent-primary);
53
+ font-weight: 500;
54
+ }
55
+
56
+ .icon {
57
+ display: flex;
58
+ align-items: center;
59
+ flex-shrink: 0;
60
+ }
61
+
62
+ .subLabel {
63
+ font-size: var(--rs-font-size-small);
64
+ font-weight: 500;
65
+ color: var(--rs-color-foreground-base-secondary);
66
+ margin-top: var(--rs-space-3);
67
+ display: block;
68
+ white-space: nowrap;
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ }