@raystack/chronicle 0.6.1 → 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.
@@ -15,23 +15,8 @@ interface ChapterNavProps {
15
15
  tree: Root;
16
16
  }
17
17
 
18
- function buildChapterIndices(
19
- children: Node[]
20
- ): Map<Node, number> {
21
- const indices = new Map<Node, number>();
22
- let index = 0;
23
- for (const item of children) {
24
- if (item.type === 'folder') {
25
- index++;
26
- indices.set(item, index);
27
- }
28
- }
29
- return indices;
30
- }
31
-
32
18
  export function ChapterNav({ tree }: ChapterNavProps) {
33
19
  const { pathname } = useLocation();
34
- const chapterIndices = buildChapterIndices(tree.children);
35
20
 
36
21
  return (
37
22
  <nav className={styles.nav}>
@@ -40,11 +25,10 @@ export function ChapterNav({ tree }: ChapterNavProps) {
40
25
  if (item.type === 'separator') return null;
41
26
 
42
27
  if (item.type === 'folder') {
43
- const chapterIndex = chapterIndices.get(item) ?? 0;
44
28
  return (
45
29
  <li key={item.name?.toString()} className={styles.chapter}>
46
30
  <span className={styles.chapterLabel}>
47
- {String(chapterIndex).padStart(2, '0')}. {item.name}
31
+ {item.name}
48
32
  </span>
49
33
  <ul className={styles.chapterItems}>
50
34
  {item.children.map(child => (
@@ -1,5 +1,17 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Hanuman:wght@400;700&display=swap");
2
+
3
+ @font-face {
4
+ font-family: "Departure Mono";
5
+ src: url("./fonts/DepartureMono-Regular.woff2") format("woff2");
6
+ font-weight: 400;
7
+ font-style: normal;
8
+ font-display: swap;
9
+ }
10
+
1
11
  .layout {
2
- --paper-sidebar-width: 260px;
12
+ --paper-sidebar-width: 262px;
13
+ --paper-font-mono: "Departure Mono", "SF Mono", "Fira Code", monospace;
14
+ --paper-font-body: "Hanuman", sans-serif;
3
15
 
4
16
  min-height: 100vh;
5
17
  }
@@ -8,33 +20,62 @@
8
20
  flex: 1;
9
21
  }
10
22
 
23
+ :global(body) {
24
+ background: var(--rs-color-background-neutral-primary);
25
+ }
26
+
11
27
  .sidebar {
12
28
  width: var(--paper-sidebar-width);
13
- padding: var(--rs-space-7) var(--rs-space-5);
29
+ display: flex;
30
+ flex-direction: column;
31
+ height: 100vh;
32
+ position: sticky;
33
+ top: 0;
14
34
  background: var(--rs-color-background-neutral-primary);
15
- overflow-y: auto;
16
- font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
17
35
  }
18
36
 
19
- .title {
37
+ .header {
38
+ display: flex;
39
+ align-items: center;
40
+ height: 48px;
41
+ padding: 0 var(--rs-space-5);
42
+ flex-shrink: 0;
43
+ }
44
+
45
+ .contentDirTrigger {
46
+ border: none;
47
+ outline: none;
48
+ background: var(--rs-color-background-neutral-primary);
49
+ color: var(--rs-color-foreground-accent-primary);
50
+ font-family: var(--paper-font-mono);
51
+ font-size: var(--rs-font-size-regular);
52
+ font-weight: var(--rs-font-weight-medium);
53
+ line-height: var(--rs-line-height-mini);
54
+ letter-spacing: var(--rs-letter-spacing-mini);
20
55
  text-transform: uppercase;
21
- letter-spacing: 0.08em;
56
+ }
57
+
58
+ .title {
59
+ font-size: var(--rs-font-size-regular);
60
+ letter-spacing: var(--rs-letter-spacing-mini);
22
61
  color: var(--rs-color-foreground-accent-primary);
23
- font-family: inherit;
24
- font-size: var(--rs-font-size-mono-large);
25
- margin-bottom: var(--rs-space-7);
62
+ font-family: var(--paper-font-mono);
63
+ text-transform: uppercase;
26
64
  }
27
65
 
28
- .nav {
29
- display: flex;
30
- flex-direction: column;
31
- gap: var(--rs-space-3);
32
- margin-bottom: var(--rs-space-7);
66
+ .navScroll {
67
+ flex: 1;
68
+ overflow-y: auto;
69
+ padding: var(--rs-space-5) var(--rs-space-5);
70
+ }
71
+
72
+ .footer {
73
+ flex-shrink: 0;
74
+ padding: var(--rs-space-4) var(--rs-space-5);
75
+ border-top: 1px solid var(--rs-color-border-base-primary);
33
76
  }
34
77
 
35
78
  .content {
36
79
  flex: 1;
37
- overflow-y: auto;
38
80
  background: var(--rs-color-background-neutral-primary);
39
- padding-right: var(--paper-sidebar-width);
40
81
  }
@@ -1,40 +1,86 @@
1
1
  'use client';
2
2
 
3
- import { Flex, Headline } from '@raystack/apsara';
3
+ import { Flex, Select, Text } from '@raystack/apsara';
4
4
  import { cx } from 'class-variance-authority';
5
+ import { useLocation, useNavigate } from 'react-router';
6
+ import { getLandingEntries } from '@/lib/config';
7
+ import { getActiveContentDir } from '@/lib/navigation';
8
+ import { usePageContext } from '@/lib/page-context';
5
9
  import type { ThemeLayoutProps } from '@/types';
6
10
  import { ChapterNav } from './ChapterNav';
7
- import { ContentDirDropdown } from './ContentDirDropdown';
8
11
  import styles from './Layout.module.css';
12
+ import { ReaderModeProvider, useReaderMode } from './ReaderModeContext';
9
13
  import { VersionSwitcher } from './VersionSwitcher';
10
14
 
11
- export function Layout({
15
+ function SidebarHeader({ config }: { config: ThemeLayoutProps['config'] }) {
16
+ const { version } = usePageContext();
17
+ const { pathname } = useLocation();
18
+ const navigate = useNavigate();
19
+
20
+ const entries = getLandingEntries(config, version.dir);
21
+
22
+ if (entries.length <= 1) {
23
+ return (
24
+ <Text size={2} weight={500} className={styles.title}>
25
+ {config.site.title}
26
+ </Text>
27
+ );
28
+ }
29
+
30
+ const activeDir = getActiveContentDir(pathname, config);
31
+ const activeEntry =
32
+ entries.find(e => e.contentDir === activeDir) ?? entries[0];
33
+
34
+ return (
35
+ <Select
36
+ value={activeEntry.contentDir}
37
+ onValueChange={(val: string) => {
38
+ const entry = entries.find(e => e.contentDir === val);
39
+ if (entry) navigate(entry.href);
40
+ }}
41
+ >
42
+ <Select.Trigger size='small' className={styles.contentDirTrigger}>
43
+ <Select.Value placeholder={activeEntry.label} className={styles.title} />
44
+ </Select.Trigger>
45
+ <Select.Content>
46
+ {entries.map(entry => (
47
+ <Select.Item key={entry.href} value={entry.contentDir}>
48
+ {entry.label}
49
+ </Select.Item>
50
+ ))}
51
+ </Select.Content>
52
+ </Select>
53
+ );
54
+ }
55
+
56
+ function LayoutInner({
12
57
  children,
13
58
  config,
14
59
  tree,
15
60
  hideSidebar,
16
61
  classNames
17
62
  }: ThemeLayoutProps) {
63
+ const { readerMode } = useReaderMode();
64
+ const showSidebar = !hideSidebar && !readerMode;
65
+
18
66
  return (
19
67
  <Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
20
68
  <Flex className={cx(styles.body, classNames?.body)}>
21
- {hideSidebar ? null : (
69
+ {showSidebar ? (
22
70
  <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 />
71
+ <div className={styles.header}>
72
+ <SidebarHeader config={config} />
34
73
  </div>
35
- <ChapterNav tree={tree} />
74
+ <div className={styles.navScroll}>
75
+ <ChapterNav tree={tree} />
76
+ </div>
77
+ {config.versions?.length ? (
78
+ <div className={styles.footer}>
79
+ <VersionSwitcher />
80
+ </div>
81
+ ) : null}
36
82
  </aside>
37
- )}
83
+ ) : null}
38
84
  <div className={cx(styles.content, classNames?.content)}>
39
85
  {children}
40
86
  </div>
@@ -42,3 +88,11 @@ export function Layout({
42
88
  </Flex>
43
89
  );
44
90
  }
91
+
92
+ export function Layout(props: ThemeLayoutProps) {
93
+ return (
94
+ <ReaderModeProvider>
95
+ <LayoutInner {...props} />
96
+ </ReaderModeProvider>
97
+ );
98
+ }
@@ -1,76 +1,89 @@
1
1
  .main {
2
- --paper-navbar-height: 40px;
3
- --paper-navbar-padding: var(--rs-space-3);
4
- --paper-navbar-total: calc(
5
- var(--paper-navbar-height) +
6
- var(--paper-navbar-padding) *
7
- 2 +
8
- 1px
9
- );
10
-
11
2
  flex: 1;
12
- max-width: 1024px;
3
+ width: 90%;
4
+ max-width: calc(1024px + var(--rs-space-17));
5
+ margin: 0 auto;
6
+ padding-top: var(--rs-space-12);
7
+ padding-right: var(--rs-space-17);
8
+ }
9
+
10
+ .readerMode {
11
+ padding-right: 0;
13
12
  margin: 0 auto;
14
13
  }
15
14
 
16
15
  .navbar {
17
- height: var(--paper-navbar-height);
18
- padding: var(--paper-navbar-padding) 0;
19
- border-bottom: 1px solid var(--rs-color-border-base-primary);
16
+ display: flex;
17
+ align-items: center;
20
18
  justify-content: space-between;
21
- width: 100%;
22
- position: fixed;
23
- top: 0;
19
+ height: 48px;
20
+ padding: var(--rs-space-2) var(--rs-space-7);
24
21
  background: var(--rs-color-background-neutral-primary);
22
+ backdrop-filter: blur(8px);
23
+ border-bottom: 0.5px solid var(--rs-color-border-base-primary);
24
+ position: sticky;
25
+ top: 0;
25
26
  z-index: 10;
26
- max-width: 1024px;
27
27
  }
28
28
 
29
29
  .navLeft {
30
+ display: flex;
30
31
  align-items: center;
32
+ gap: var(--rs-space-3);
31
33
  }
32
34
 
33
35
  .navRight {
36
+ display: flex;
34
37
  align-items: center;
38
+ gap: var(--rs-space-3);
35
39
  }
36
40
 
37
- .arrow {
41
+ .arrows {
38
42
  display: flex;
39
43
  align-items: center;
40
- color: var(--rs-color-foreground-base-primary);
44
+ gap: var(--rs-space-2);
45
+ }
46
+
47
+ .arrowLink {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
41
51
  text-decoration: none;
52
+ color: var(--rs-color-foreground-base-primary);
53
+ padding: var(--rs-space-1);
54
+ border-radius: var(--rs-radius-2);
42
55
  }
43
56
 
44
- .arrow:hover {
57
+ .arrowLink:hover {
45
58
  color: var(--rs-color-foreground-accent-primary);
46
59
  }
47
60
 
48
61
  .arrowDisabled {
49
62
  display: flex;
50
63
  align-items: center;
64
+ justify-content: center;
51
65
  color: var(--rs-color-foreground-base-tertiary);
52
66
  opacity: 0.4;
53
- cursor: default;
54
- border: none;
55
- background: none;
56
- padding: 0;
67
+ padding: var(--rs-space-1);
57
68
  }
58
69
 
59
70
  .breadcrumb {
60
- font-family: "SF Mono", "Fira Code", monospace;
71
+ display: flex;
72
+ align-items: center;
73
+ font-family: var(--paper-font-mono);
61
74
  font-size: var(--rs-font-size-small);
62
- text-transform: uppercase;
63
- letter-spacing: 0.05em;
64
- margin-left: var(--rs-space-3);
75
+ line-height: var(--rs-line-height-small);
76
+ letter-spacing: var(--rs-letter-spacing-small);
65
77
  }
66
78
 
67
79
  .separator {
68
- margin: 0 var(--rs-space-2);
80
+ margin: 0 var(--rs-space-1);
69
81
  color: var(--rs-color-foreground-base-tertiary);
70
82
  }
71
83
 
72
84
  .crumbLink {
73
85
  color: var(--rs-color-foreground-base-tertiary);
86
+ font-weight: var(--rs-font-weight-medium);
74
87
  text-decoration: none;
75
88
  }
76
89
 
@@ -80,29 +93,67 @@
80
93
 
81
94
  .crumbActive {
82
95
  color: var(--rs-color-foreground-base-primary);
83
- font-weight: 600;
96
+ font-weight: var(--rs-font-weight-medium);
84
97
  }
85
98
 
86
99
  .article {
87
100
  flex: 1;
88
101
  min-width: 0;
89
- margin-top: var(--paper-navbar-total);
102
+ width: 100%;
90
103
  padding: 0 var(--rs-space-7);
91
104
  }
92
105
 
93
- .searchButton {
94
- height: 28px;
95
- padding: 0 var(--rs-space-3);
106
+ .articleHeader {
107
+ text-align: center;
108
+ max-width: 656px;
109
+ margin: 0 auto;
110
+ }
111
+
112
+ .readingTime {
113
+ display: block;
114
+ font-family: var(--paper-font-mono);
96
115
  font-size: var(--rs-font-size-small);
116
+ font-weight: var(--rs-font-weight-regular);
117
+ line-height: 1.67;
118
+ letter-spacing: var(--rs-letter-spacing-t1);
119
+ color: var(--rs-color-foreground-base-tertiary);
120
+ margin-bottom: var(--rs-space-5);
121
+ }
122
+
123
+ .articleTitle {
124
+ font-family: var(--paper-font-body);
125
+ font-size: var(--rs-space-8);
126
+ font-weight: var(--rs-font-weight-medium);
127
+ line-height: var(--rs-space-10);
128
+ letter-spacing: var(--rs-letter-spacing-t1);
129
+ text-align: center;
130
+ color: var(--rs-color-foreground-base-primary);
131
+ margin: 0;
132
+ }
133
+
134
+ .articleSeparator {
135
+ width: 55px;
97
136
  border: none;
98
- box-shadow: none;
137
+ border-top: 1px solid var(--rs-color-border-base-primary);
138
+ margin: var(--rs-space-10) auto;
139
+ }
140
+
141
+ .articleDescription {
142
+ font-family: var(--paper-font-body);
143
+ font-size: var(--rs-space-4);
144
+ font-weight: var(--rs-font-weight-medium);
145
+ line-height: var(--rs-space-7);
146
+ letter-spacing: var(--rs-letter-spacing-t1);
147
+ text-align: center;
148
+ color: var(--rs-color-foreground-base-secondary);
149
+ margin: var(--rs-space-4) 0 0;
99
150
  }
100
151
 
101
152
  .content {
102
- font-family: Georgia, "Times New Roman", serif;
153
+ font-family: var(--paper-font-body);
103
154
  line-height: 1.8;
104
155
  background: var(--rs-color-background-base-primary);
105
- padding: var(--rs-space-9);
156
+ padding: var(--rs-space-15) var(--rs-space-9) var(--rs-space-9);
106
157
  border-left: 1px solid var(--rs-color-border-base-primary);
107
158
  border-right: 1px solid var(--rs-color-border-base-primary);
108
159
  box-shadow:
@@ -152,6 +203,7 @@
152
203
 
153
204
  .content p {
154
205
  margin: 0.75rem 0;
206
+ line-height: 2;
155
207
  }
156
208
 
157
209
  .content ul,
@@ -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