@raystack/chronicle 0.6.0 → 0.7.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.
@@ -1,16 +1,31 @@
1
- import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
2
- import { Flex } from '@raystack/apsara';
3
- import { useMemo } from 'react';
1
+ import {
2
+ ArrowLeftIcon,
3
+ ArrowRightIcon,
4
+ ChevronRightIcon,
5
+ AdjustmentsHorizontalIcon,
6
+ EyeIcon,
7
+ SunIcon,
8
+ MoonIcon,
9
+ XMarkIcon,
10
+ } from '@heroicons/react/24/outline';
11
+ import { IconButton, useTheme } from '@raystack/apsara';
12
+ import { useEffect, useMemo, useState } from 'react';
4
13
  import { Link as RouterLink, useLocation } from 'react-router';
5
14
  import { getBreadcrumbItems } from 'fumadocs-core/breadcrumb';
6
15
  import { flattenTree } from 'fumadocs-core/page-tree';
7
- import { Search } from '@/components/ui/search';
8
16
  import type { ThemePageProps } from '@/types';
9
17
  import styles from './Page.module.css';
18
+ import { useReaderMode } from './ReaderModeContext';
10
19
  import { ReadingProgress } from './ReadingProgress';
11
20
 
12
- export function Page({ page, config, tree }: ThemePageProps) {
21
+ export function Page({ page, tree }: ThemePageProps) {
13
22
  const { pathname } = useLocation();
23
+ const [settingsOpen, setSettingsOpen] = useState(false);
24
+ const [isClient, setIsClient] = useState(false);
25
+ const { resolvedTheme, setTheme } = useTheme();
26
+ const { readerMode, toggleReaderMode } = useReaderMode();
27
+
28
+ useEffect(() => { setIsClient(true); }, []);
14
29
 
15
30
  const { prev, next, crumbs } = useMemo(() => {
16
31
  const pages = flattenTree(tree.children);
@@ -32,47 +47,33 @@ export function Page({ page, config, tree }: ThemePageProps) {
32
47
 
33
48
  return (
34
49
  <>
35
- <main className={styles.main}>
36
- <Flex align='center' className={styles.navbar}>
37
- <Flex align='center' gap='small' className={styles.navLeft}>
38
- {prev ? (
39
- <RouterLink
40
- to={prev.url}
41
- className={styles.arrow}
42
- aria-label='Previous page'
43
- >
44
- <ChevronLeftIcon width={14} height={14} />
45
- </RouterLink>
46
- ) : (
47
- <button
48
- disabled
49
- className={styles.arrowDisabled}
50
- aria-label='Previous page'
51
- >
52
- <ChevronLeftIcon width={14} height={14} />
53
- </button>
54
- )}
55
- {next ? (
56
- <RouterLink
57
- to={next.url}
58
- className={styles.arrow}
59
- aria-label='Next page'
60
- >
61
- <ChevronRightIcon width={14} height={14} />
62
- </RouterLink>
63
- ) : (
64
- <button
65
- disabled
66
- className={styles.arrowDisabled}
67
- aria-label='Next page'
68
- >
69
- <ChevronRightIcon width={14} height={14} />
70
- </button>
71
- )}
50
+ <main className={`${styles.main} ${readerMode ? styles.readerMode : ''}`}>
51
+ <div className={styles.navbar}>
52
+ <div className={styles.navLeft}>
53
+ <div className={styles.arrows}>
54
+ {prev ? (
55
+ <RouterLink to={prev.url} className={styles.arrowLink} aria-label='Previous page'>
56
+ <ArrowLeftIcon width={14} height={14} />
57
+ </RouterLink>
58
+ ) : (
59
+ <span className={styles.arrowDisabled} aria-hidden='true'>
60
+ <ArrowLeftIcon width={14} height={14} />
61
+ </span>
62
+ )}
63
+ {next ? (
64
+ <RouterLink to={next.url} className={styles.arrowLink} aria-label='Next page'>
65
+ <ArrowRightIcon width={14} height={14} />
66
+ </RouterLink>
67
+ ) : (
68
+ <span className={styles.arrowDisabled} aria-hidden='true'>
69
+ <ArrowRightIcon width={14} height={14} />
70
+ </span>
71
+ )}
72
+ </div>
72
73
  <nav className={styles.breadcrumb}>
73
74
  {crumbs.map((crumb, i) => (
74
75
  <span key={crumb.href}>
75
- {i > 0 && <span className={styles.separator}>/</span>}
76
+ {i > 0 && <ChevronRightIcon width={12} height={12} className={styles.separator} />}
76
77
  {i === crumbs.length - 1 ? (
77
78
  <span className={styles.crumbActive}>{crumb.label}</span>
78
79
  ) : (
@@ -83,18 +84,53 @@ export function Page({ page, config, tree }: ThemePageProps) {
83
84
  </span>
84
85
  ))}
85
86
  </nav>
86
- </Flex>
87
- <Flex align='center' className={styles.navRight}>
88
- {config.search?.enabled && (
89
- <Search className={styles.searchButton} />
87
+ </div>
88
+ <div className={styles.navRight}>
89
+ {settingsOpen ? (
90
+ <>
91
+ <IconButton size={2} onClick={toggleReaderMode} aria-label='Toggle reader mode'>
92
+ <EyeIcon width={14} height={14} />
93
+ </IconButton>
94
+ {isClient && (
95
+ <IconButton
96
+ size={2}
97
+ onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
98
+ aria-label={resolvedTheme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
99
+ >
100
+ {resolvedTheme === 'dark'
101
+ ? <SunIcon width={14} height={14} />
102
+ : <MoonIcon width={14} height={14} />
103
+ }
104
+ </IconButton>
105
+ )}
106
+ <IconButton size={2} onClick={() => setSettingsOpen(false)} aria-label='Close settings'>
107
+ <XMarkIcon width={14} height={14} />
108
+ </IconButton>
109
+ </>
110
+ ) : (
111
+ <IconButton size={2} onClick={() => setSettingsOpen(true)} aria-label='Open settings'>
112
+ <AdjustmentsHorizontalIcon width={14} height={14} />
113
+ </IconButton>
114
+ )}
115
+ </div>
116
+ </div>
117
+ <div className={styles.content}>
118
+ <header className={styles.articleHeader}>
119
+ {page.frontmatter._readingTime && (
120
+ <span className={styles.readingTime}>{page.frontmatter._readingTime}min Read</span>
121
+ )}
122
+ <h1 className={styles.articleTitle}>{page.frontmatter.title}</h1>
123
+ {page.frontmatter.description && (
124
+ <p className={styles.articleDescription}>{page.frontmatter.description}</p>
90
125
  )}
91
- </Flex>
92
- </Flex>
93
- <article className={styles.article} data-article-content>
94
- <div className={styles.content}>{page.content}</div>
95
- </article>
126
+ <hr className={styles.articleSeparator} />
127
+ </header>
128
+ <article className={styles.article} data-article-content>
129
+ {page.content}
130
+ </article>
131
+ </div>
96
132
  </main>
97
- <ReadingProgress items={page.toc} />
133
+ {!readerMode && <ReadingProgress items={page.toc} />}
98
134
  </>
99
135
  );
100
136
  }
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, useState, type ReactNode } from 'react';
4
+
5
+ interface ReaderModeContextValue {
6
+ readerMode: boolean;
7
+ toggleReaderMode: () => void;
8
+ }
9
+
10
+ const ReaderModeContext = createContext<ReaderModeContextValue>({
11
+ readerMode: false,
12
+ toggleReaderMode: () => {},
13
+ });
14
+
15
+ export function ReaderModeProvider({ children }: { children: ReactNode }) {
16
+ const [readerMode, setReaderMode] = useState(false);
17
+ return (
18
+ <ReaderModeContext.Provider
19
+ value={{ readerMode, toggleReaderMode: () => setReaderMode(v => !v) }}
20
+ >
21
+ {children}
22
+ </ReaderModeContext.Provider>
23
+ );
24
+ }
25
+
26
+ export function useReaderMode() {
27
+ return useContext(ReaderModeContext);
28
+ }
@@ -220,6 +220,7 @@ export function ReadingProgress({ items }: ReadingProgressProps) {
220
220
  const element = document.getElementById(id);
221
221
  if (!element) return;
222
222
 
223
+ history.pushState(null, '', `#${id}`);
223
224
  const elementTop = element.getBoundingClientRect().top + window.scrollY;
224
225
  window.scrollTo({
225
226
  top: Math.max(0, elementTop - NAV_HEIGHT),
@@ -15,7 +15,7 @@ export function getTheme(name?: string): Theme {
15
15
 
16
16
  export function getThemeConfig(name?: string) {
17
17
  if (name === 'paper') {
18
- return { enableSystem: false, forcedTheme: 'light' };
18
+ return { enableSystem: true };
19
19
  }
20
20
  return { enableSystem: true };
21
21
  }
@@ -84,6 +84,7 @@ const dirNameSchema = z
84
84
  const contentEntrySchema = z.object({
85
85
  dir: dirNameSchema,
86
86
  label: z.string().min(1),
87
+ description: z.string().optional(),
87
88
  icon: z.string().optional(),
88
89
  })
89
90
 
@@ -10,6 +10,7 @@ export interface Frontmatter {
10
10
  order?: number
11
11
  icon?: string
12
12
  lastModified?: string
13
+ _readingTime?: number
13
14
  }
14
15
 
15
16
  export interface PageNavLink {