@raystack/chronicle 0.1.0-canary.6511afe → 0.1.0-canary.67113f8

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.
@@ -1,46 +1,30 @@
1
1
  'use client';
2
2
 
3
3
  import { Text } from '@raystack/apsara';
4
- import { useEffect, useState } from 'react';
5
- import type { TocItem } from '@/types';
4
+ import { AnchorProvider, useActiveAnchor } from 'fumadocs-core/toc';
5
+ import type { TableOfContents, TOCItemType } from 'fumadocs-core/toc';
6
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>('');
14
-
15
- // Filter to only show h2 and h3 headings
16
13
  const filteredItems = items.filter(
17
14
  item => item.depth >= 2 && item.depth <= 3
18
15
  );
19
16
 
20
- useEffect(() => {
21
- const headingIds = filteredItems.map(item => item.url.replace('#', ''));
22
-
23
- const observer = new IntersectionObserver(
24
- entries => {
25
- entries.forEach(entry => {
26
- if (entry.isIntersecting) {
27
- setActiveId(entry.target.id);
28
- }
29
- });
30
- },
31
- // -80px top: offset for fixed header, -80% bottom: trigger when heading is in top 20% of viewport
32
- { rootMargin: '-80px 0px -80% 0px' }
33
- );
34
-
35
- headingIds.forEach(id => {
36
- const element = document.getElementById(id);
37
- if (element) observer.observe(element);
38
- });
17
+ if (filteredItems.length === 0) return null;
39
18
 
40
- return () => observer.disconnect();
41
- }, [filteredItems]);
19
+ return (
20
+ <AnchorProvider toc={filteredItems} single>
21
+ <TocContent items={filteredItems} />
22
+ </AnchorProvider>
23
+ );
24
+ }
42
25
 
43
- if (filteredItems.length === 0) return null;
26
+ function TocContent({ items }: { items: TOCItemType[] }) {
27
+ const activeAnchor = useActiveAnchor();
44
28
 
45
29
  return (
46
30
  <aside className={styles.toc}>
@@ -48,9 +32,9 @@ export function Toc({ items }: TocProps) {
48
32
  On this page
49
33
  </Text>
50
34
  <nav className={styles.nav}>
51
- {filteredItems.map(item => {
35
+ {items.map(item => {
52
36
  const id = item.url.replace('#', '');
53
- const isActive = activeId === id;
37
+ const isActive = activeAnchor === id;
54
38
  const isNested = item.depth > 2;
55
39
  return (
56
40
  <a
@@ -1,6 +1,6 @@
1
1
  import { Link as RouterLink, useLocation } from 'react-router';
2
2
  import { MethodBadge } from '@/components/api/method-badge';
3
- import type { PageTree, PageTreeItem } from '@/types';
3
+ import type { Root, Node } from 'fumadocs-core/page-tree';
4
4
  import styles from './ChapterNav.module.css';
5
5
 
6
6
  const iconMap: Record<string, React.ReactNode> = {
@@ -12,16 +12,16 @@ const iconMap: Record<string, React.ReactNode> = {
12
12
  };
13
13
 
14
14
  interface ChapterNavProps {
15
- tree: PageTree;
15
+ tree: Root;
16
16
  }
17
17
 
18
18
  function buildChapterIndices(
19
- children: PageTreeItem[]
20
- ): Map<PageTreeItem, number> {
21
- const indices = new Map<PageTreeItem, number>();
19
+ children: Node[]
20
+ ): Map<Node, number> {
21
+ const indices = new Map<Node, number>();
22
22
  let index = 0;
23
23
  for (const item of children) {
24
- if (item.type === 'folder' && item.children) {
24
+ if (item.type === 'folder') {
25
25
  index++;
26
26
  indices.set(item, index);
27
27
  }
@@ -39,17 +39,17 @@ export function ChapterNav({ tree }: ChapterNavProps) {
39
39
  {tree.children.map(item => {
40
40
  if (item.type === 'separator') return null;
41
41
 
42
- if (item.type === 'folder' && item.children) {
42
+ if (item.type === 'folder') {
43
43
  const chapterIndex = chapterIndices.get(item) ?? 0;
44
44
  return (
45
- <li key={item.name} className={styles.chapter}>
45
+ <li key={item.name?.toString()} className={styles.chapter}>
46
46
  <span className={styles.chapterLabel}>
47
47
  {String(chapterIndex).padStart(2, '0')}. {item.name}
48
48
  </span>
49
49
  <ul className={styles.chapterItems}>
50
50
  {item.children.map(child => (
51
51
  <ChapterItem
52
- key={child.url ?? child.name}
52
+ key={child.type === 'page' ? child.url : (child.name?.toString() ?? '')}
53
53
  item={child}
54
54
  pathname={pathname}
55
55
  />
@@ -61,7 +61,7 @@ export function ChapterNav({ tree }: ChapterNavProps) {
61
61
 
62
62
  return (
63
63
  <ChapterItem
64
- key={item.url ?? item.name}
64
+ key={item.url ?? item.name?.toString() ?? ''}
65
65
  item={item}
66
66
  pathname={pathname}
67
67
  />
@@ -76,19 +76,19 @@ function ChapterItem({
76
76
  item,
77
77
  pathname
78
78
  }: {
79
- item: PageTreeItem;
79
+ item: Node;
80
80
  pathname: string;
81
81
  }) {
82
82
  if (item.type === 'separator') return null;
83
83
 
84
- if (item.type === 'folder' && item.children) {
84
+ if (item.type === 'folder') {
85
85
  return (
86
86
  <li>
87
87
  <span className={styles.subLabel}>{item.name}</span>
88
88
  <ul className={styles.chapterItems}>
89
89
  {item.children.map(child => (
90
90
  <ChapterItem
91
- key={child.url ?? child.name}
91
+ key={child.type === 'page' ? child.url : (child.name?.toString() ?? '')}
92
92
  item={child}
93
93
  pathname={pathname}
94
94
  />
@@ -99,7 +99,7 @@ function ChapterItem({
99
99
  }
100
100
 
101
101
  const isActive = pathname === item.url;
102
- const icon = item.icon ? iconMap[item.icon] : null;
102
+ const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
103
103
 
104
104
  return (
105
105
  <li>
@@ -174,6 +174,11 @@
174
174
  margin-bottom: var(--rs-space-3);
175
175
  }
176
176
 
177
+ .content img {
178
+ max-width: 100%;
179
+ height: auto;
180
+ }
181
+
177
182
  .content blockquote {
178
183
  margin: 1rem 0;
179
184
  padding-left: 1rem;
@@ -2,59 +2,33 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
2
2
  import { Flex } from '@raystack/apsara';
3
3
  import { useMemo } from 'react';
4
4
  import { Link as RouterLink, useLocation } from 'react-router';
5
+ import { getBreadcrumbItems } from 'fumadocs-core/breadcrumb';
6
+ import { flattenTree } from 'fumadocs-core/page-tree';
5
7
  import { Search } from '@/components/ui/search';
6
- import type { PageTreeItem, ThemePageProps } from '@/types';
8
+ import type { ThemePageProps } from '@/types';
7
9
  import styles from './Page.module.css';
8
10
  import { ReadingProgress } from './ReadingProgress';
9
11
 
10
- function flattenTree(items: PageTreeItem[]): PageTreeItem[] {
11
- const result: PageTreeItem[] = [];
12
- for (const item of items) {
13
- if (item.type === 'page' && item.url) result.push(item);
14
- if (item.children) result.push(...flattenTree(item.children));
15
- }
16
- return result;
17
- }
18
-
19
- function findBreadcrumb(
20
- items: PageTreeItem[],
21
- slug: string[]
22
- ): { label: string; href: string }[] {
23
- const result: { label: string; href: string }[] = [];
24
- for (let i = 0; i < slug.length; i++) {
25
- const path = '/' + slug.slice(0, i + 1).join('/');
26
- const found = findInTree(items, path);
27
- result.push({ label: found?.name ?? slug[i], href: path });
28
- }
29
- return result;
30
- }
31
-
32
- function findInTree(
33
- items: PageTreeItem[],
34
- path: string
35
- ): PageTreeItem | undefined {
36
- for (const item of items) {
37
- if (item.url === path) return item;
38
- if (item.children) {
39
- const found = findInTree(item.children, path);
40
- if (found) return found;
41
- }
42
- }
43
- return undefined;
44
- }
45
-
46
12
  export function Page({ page, config, tree }: ThemePageProps) {
47
13
  const { pathname } = useLocation();
48
14
 
49
15
  const { prev, next, crumbs } = useMemo(() => {
50
16
  const pages = flattenTree(tree.children);
51
17
  const currentIndex = pages.findIndex(p => p.url === pathname);
18
+ const breadcrumbItems = getBreadcrumbItems(
19
+ pathname,
20
+ tree,
21
+ { includePage: true }
22
+ );
52
23
  return {
53
24
  prev: currentIndex > 0 ? pages[currentIndex - 1] : null,
54
25
  next: currentIndex < pages.length - 1 ? pages[currentIndex + 1] : null,
55
- crumbs: findBreadcrumb(tree.children, page.slug)
26
+ crumbs: breadcrumbItems.map(item => ({
27
+ label: item.name,
28
+ href: item.url ?? pathname,
29
+ })),
56
30
  };
57
- }, [tree, pathname, page.slug]);
31
+ }, [tree, pathname]);
58
32
 
59
33
  return (
60
34
  <>
@@ -63,7 +37,7 @@ export function Page({ page, config, tree }: ThemePageProps) {
63
37
  <Flex align='center' gap='small' className={styles.navLeft}>
64
38
  {prev ? (
65
39
  <RouterLink
66
- to={prev.url!}
40
+ to={prev.url}
67
41
  className={styles.arrow}
68
42
  aria-label='Previous page'
69
43
  >
@@ -80,7 +54,7 @@ export function Page({ page, config, tree }: ThemePageProps) {
80
54
  )}
81
55
  {next ? (
82
56
  <RouterLink
83
- to={next.url!}
57
+ to={next.url}
84
58
  className={styles.arrow}
85
59
  aria-label='Next page'
86
60
  >
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { cx } from 'class-variance-authority';
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
- import type { TocItem } from '@/types';
5
+ import type { TOCItemType } from 'fumadocs-core/toc';
6
6
  import styles from './ReadingProgress.module.css';
7
7
 
8
8
  interface Heading {
@@ -68,7 +68,7 @@ function resolveOverlaps(headings: Heading[], maxPosition: number): Heading[] {
68
68
  }
69
69
 
70
70
  interface ReadingProgressProps {
71
- items: TocItem[];
71
+ items: TOCItemType[];
72
72
  }
73
73
 
74
74
  export function ReadingProgress({ items }: ReadingProgressProps) {
@@ -1,4 +1,8 @@
1
1
  import type { ReactNode } from 'react'
2
+ import type { TableOfContents } from 'fumadocs-core/toc'
3
+
4
+ export type { Root, Node, Item, Folder, Separator } from 'fumadocs-core/page-tree'
5
+ export type { TOCItemType, TableOfContents } from 'fumadocs-core/toc'
2
6
 
3
7
  export interface Frontmatter {
4
8
  title: string
@@ -12,25 +16,5 @@ export interface Page {
12
16
  slug: string[]
13
17
  frontmatter: Frontmatter
14
18
  content: ReactNode
15
- toc: TocItem[]
16
- }
17
-
18
- export interface TocItem {
19
- title: string
20
- url: string
21
- depth: number
22
- }
23
-
24
- export interface PageTreeItem {
25
- type: 'page' | 'folder' | 'separator'
26
- name: string
27
- url?: string
28
- order?: number
29
- icon?: string
30
- children?: PageTreeItem[]
31
- }
32
-
33
- export interface PageTree {
34
- name: string
35
- children: PageTreeItem[]
19
+ toc: TableOfContents
36
20
  }
@@ -1,4 +1,3 @@
1
1
  // Vite build-time constants (injected via define in vite-config.ts)
2
2
  declare const __CHRONICLE_CONTENT_DIR__: string
3
3
  declare const __CHRONICLE_PROJECT_ROOT__: string
4
- declare const __CHRONICLE_PACKAGE_ROOT__: string
@@ -1,18 +1,19 @@
1
1
  import type { ReactNode } from 'react'
2
+ import type { Root } from 'fumadocs-core/page-tree'
2
3
  import type { ChronicleConfig } from './config'
3
- import type { Page, PageTree } from './content'
4
+ import type { Page } from './content'
4
5
 
5
6
  export interface ThemeLayoutProps {
6
7
  children: ReactNode
7
8
  config: ChronicleConfig
8
- tree: PageTree
9
+ tree: Root
9
10
  classNames?: { layout?: string; body?: string; sidebar?: string; content?: string }
10
11
  }
11
12
 
12
13
  export interface ThemePageProps {
13
14
  page: Page
14
15
  config: ChronicleConfig
15
- tree: PageTree
16
+ tree: Root
16
17
  }
17
18
 
18
19
  export interface Theme {