astro-better-docs-sidebar 0.1.0 → 1.0.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.
- package/README.md +32 -0
- package/Sidebar.astro +65 -65
- package/SidebarItem.astro +3 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,6 +122,38 @@ hidden: true
|
|
|
122
122
|
---
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
## Non-routable folder index pages
|
|
126
|
+
|
|
127
|
+
Set `route: false` in a folder's `index.mdx` to prevent the sidebar from linking to it while still using it to set the folder's title and `order`. The folder heading appears as a plain label with no link.
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
# docs/core-concepts/index.mdx
|
|
131
|
+
---
|
|
132
|
+
title: Core Concepts
|
|
133
|
+
order: 2
|
|
134
|
+
route: false
|
|
135
|
+
---
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This is equivalent to `sidebar.folderBehavior: unclickable` but scoped to the index page itself rather than the `folderBehavior` prop. Use it when a folder should never have a clickable header, regardless of the global `folderBehavior` setting.
|
|
139
|
+
|
|
140
|
+
Make sure your content collection schema includes `route`:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const docs = defineCollection({
|
|
144
|
+
schema: z.object({
|
|
145
|
+
title: z.string(),
|
|
146
|
+
order: z.number().optional(),
|
|
147
|
+
hidden: z.boolean().optional(),
|
|
148
|
+
route: z.boolean().optional(),
|
|
149
|
+
sidebar: z.object({
|
|
150
|
+
label: z.string().optional(),
|
|
151
|
+
folderBehavior: z.enum(['page', 'unclickable', 'overview']).optional(),
|
|
152
|
+
}).optional(),
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
125
157
|
## Controlling sort order
|
|
126
158
|
|
|
127
159
|
Set `order` in front matter to control sort position within a folder. Lower numbers sort first. Pages without `order` sort last, then alphabetically by title.
|
package/Sidebar.astro
CHANGED
|
@@ -52,27 +52,30 @@ function buildTree(pages: any[]) {
|
|
|
52
52
|
|
|
53
53
|
if (part === 'index') {
|
|
54
54
|
const effective: FolderBehavior = pageFolderBehavior || folderBehavior;
|
|
55
|
-
const href = `${basePath}/${slug.replace(/\/index$/, "")}`.replace(/\/+/g, '/').replace(/\/$/, "");
|
|
56
55
|
|
|
57
56
|
current.title = label || dataTitle || current.title;
|
|
58
57
|
if (order !== undefined) current.order = order;
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
// route: false pages still contribute title/order but produce no link
|
|
60
|
+
if (page.data?.route !== false) {
|
|
61
|
+
const href = `${basePath}/${slug.replace(/\/index$/, "")}`.replace(/\/+/g, '/').replace(/\/$/, "");
|
|
62
|
+
if (effective === 'page') {
|
|
63
|
+
current.href = href;
|
|
64
|
+
} else if (effective === 'overview') {
|
|
65
|
+
// inject a synthetic first child that links to the index page
|
|
66
|
+
current.children['__sidebar_overview__'] = {
|
|
67
|
+
name: '__sidebar_overview__',
|
|
68
|
+
title: overviewLabel,
|
|
69
|
+
href,
|
|
70
|
+
children: {},
|
|
71
|
+
sortedChildren: [],
|
|
72
|
+
isOpen: false,
|
|
73
|
+
isActive: false,
|
|
74
|
+
order: -Infinity,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// 'unclickable': no href set, no overview child
|
|
74
78
|
}
|
|
75
|
-
// 'unclickable': no href set, no overview child
|
|
76
79
|
break;
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -131,7 +134,7 @@ const treeArray = getSortedArray(treeRoot.children);
|
|
|
131
134
|
---
|
|
132
135
|
|
|
133
136
|
|
|
134
|
-
<nav
|
|
137
|
+
<nav>
|
|
135
138
|
{treeArray.map(node => <SidebarItem node={node} />)}
|
|
136
139
|
</nav>
|
|
137
140
|
|
|
@@ -139,61 +142,58 @@ const treeArray = getSortedArray(treeRoot.children);
|
|
|
139
142
|
@reference "tailwindcss";
|
|
140
143
|
|
|
141
144
|
aside.aside { @apply w-64 shrink-0 border-r border-gray-200 bg-white pt-2 pr-2; }
|
|
142
|
-
|
|
145
|
+
#side-nav nav { @apply flex flex-col gap-1; }
|
|
143
146
|
|
|
144
|
-
/*
|
|
145
|
-
|
|
147
|
+
/* depth line */
|
|
148
|
+
#side-nav nav details > div { @apply flex flex-col gap-0.5 pl-3 ml-3 border-l border-slate-200; }
|
|
146
149
|
|
|
147
|
-
/*
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
/* folder header row */
|
|
151
|
+
#side-nav nav summary { @apply flex items-center justify-between cursor-pointer select-none rounded gap-1 transition-colors text-slate-700; list-style: none; }
|
|
152
|
+
#side-nav nav summary::-webkit-details-marker { display: none; }
|
|
150
153
|
|
|
151
|
-
/*
|
|
152
|
-
|
|
153
|
-
a
|
|
154
|
-
span
|
|
154
|
+
/* leaf links and folder labels */
|
|
155
|
+
#side-nav nav :is(a, span) { @apply flex-1 text-sm py-1 px-2 rounded transition-colors no-underline block text-slate-700; }
|
|
156
|
+
#side-nav nav a { @apply cursor-pointer; }
|
|
157
|
+
#side-nav nav span { @apply cursor-default; }
|
|
155
158
|
|
|
156
|
-
/*
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
details[open] >
|
|
159
|
+
/* chevron */
|
|
160
|
+
#side-nav nav i { @apply flex items-center justify-center w-7 h-7 rounded text-gray-400 shrink-0 transition-transform; }
|
|
161
|
+
#side-nav nav i::after { content: ''; @apply w-[0.4rem] h-[0.4rem] border-r-2 border-b-2 border-current block -rotate-45 transition-transform box-border; }
|
|
162
|
+
#side-nav nav details[open] > summary i::after { @apply rotate-45; }
|
|
160
163
|
|
|
161
|
-
/*
|
|
162
|
-
details:not([open]) >
|
|
164
|
+
/* clicking a closed folder opens it; disables the link's own navigation */
|
|
165
|
+
#side-nav nav details:not([open]) > summary a { pointer-events: none; }
|
|
163
166
|
|
|
164
|
-
/* HOVERS
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
details[open] >
|
|
171
|
-
details[open] >
|
|
167
|
+
/* HOVERS */
|
|
168
|
+
#side-nav nav a:not(summary a):hover { @apply bg-slate-100 text-indigo-600; }
|
|
169
|
+
#side-nav nav details:not([open]) > summary:hover,
|
|
170
|
+
#side-nav nav summary:not(:has(a)):hover { @apply bg-slate-100; }
|
|
171
|
+
#side-nav nav details:not([open]) > summary:hover :is(a, span),
|
|
172
|
+
#side-nav nav summary:not(:has(a)):hover span { @apply text-indigo-600; }
|
|
173
|
+
#side-nav nav details[open] > summary:has(a) a:hover { @apply bg-slate-100 text-indigo-600; }
|
|
174
|
+
#side-nav nav details[open] > summary:has(a) i:hover { @apply bg-slate-100 text-slate-700; }
|
|
172
175
|
|
|
173
176
|
/* ACTIVE & ANCESTOR STATES */
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
#side-nav nav a[aria-current="page"],
|
|
178
|
+
#side-nav nav summary:has(> a[aria-current="page"]) :is(a, span) { @apply font-semibold text-indigo-600 bg-indigo-50; }
|
|
179
|
+
#side-nav nav a[data-a],
|
|
180
|
+
#side-nav nav summary[data-a] :is(a, span) { @apply font-semibold text-slate-900; }
|
|
181
|
+
#side-nav nav summary:has(> [aria-current="page"]) i,
|
|
182
|
+
#side-nav nav details:has([aria-current="page"]) > summary i { @apply text-indigo-500; }
|
|
183
|
+
|
|
184
|
+
/* DARK MODE */
|
|
179
185
|
.dark aside.aside { @apply border-gray-800 bg-gray-900; }
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.dark :
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.dark
|
|
186
|
-
|
|
187
|
-
.dark
|
|
188
|
-
.dark a
|
|
189
|
-
.dark
|
|
190
|
-
|
|
191
|
-
.dark
|
|
192
|
-
.dark
|
|
193
|
-
|
|
194
|
-
.dark .l[aria-current="page"],
|
|
195
|
-
.dark .s:has(> .l[aria-current="page"]) .l { @apply text-indigo-400 bg-indigo-950/40; }
|
|
196
|
-
|
|
197
|
-
.dark .l[data-a],
|
|
198
|
-
.dark .s[data-a] .l { @apply text-white; }
|
|
186
|
+
.dark #side-nav nav :is(a, span) { @apply text-slate-400; }
|
|
187
|
+
.dark #side-nav nav details > div { @apply border-slate-700; }
|
|
188
|
+
.dark #side-nav nav a:not(summary a):hover { @apply bg-slate-800 text-indigo-400; }
|
|
189
|
+
.dark #side-nav nav details:not([open]) > summary:hover,
|
|
190
|
+
.dark #side-nav nav summary:not(:has(a)):hover { @apply bg-slate-800; }
|
|
191
|
+
.dark #side-nav nav details:not([open]) > summary:hover :is(a, span),
|
|
192
|
+
.dark #side-nav nav summary:not(:has(a)):hover span { @apply text-indigo-400; }
|
|
193
|
+
.dark #side-nav nav details[open] > summary:has(a) a:hover { @apply bg-slate-800 text-indigo-400; }
|
|
194
|
+
.dark #side-nav nav details[open] > summary:has(a) i:hover { @apply bg-slate-800 text-slate-200; }
|
|
195
|
+
.dark #side-nav nav a[aria-current="page"],
|
|
196
|
+
.dark #side-nav nav summary:has(> a[aria-current="page"]) :is(a, span) { @apply text-indigo-400 bg-indigo-950/40; }
|
|
197
|
+
.dark #side-nav nav a[data-a],
|
|
198
|
+
.dark #side-nav nav summary[data-a] :is(a, span) { @apply text-white; }
|
|
199
199
|
</style>
|
package/SidebarItem.astro
CHANGED
|
@@ -11,18 +11,17 @@ const isAnc = node.isActive && !node.isCurrentPage;
|
|
|
11
11
|
|
|
12
12
|
{childNodes.length > 0 ? (
|
|
13
13
|
<details open={node.isOpen || undefined}>
|
|
14
|
-
<summary
|
|
14
|
+
<summary data-a={isAnc || undefined}>
|
|
15
15
|
{node.href ? (
|
|
16
16
|
<a
|
|
17
17
|
href={`${node.href}/`}
|
|
18
|
-
class="l"
|
|
19
18
|
aria-current={node.isCurrentPage ? "page" : undefined}
|
|
20
19
|
set:html={htmlTitle}
|
|
21
20
|
></a>
|
|
22
21
|
) : (
|
|
23
|
-
<span
|
|
22
|
+
<span set:html={htmlTitle}></span>
|
|
24
23
|
)}
|
|
25
|
-
<i
|
|
24
|
+
<i></i>
|
|
26
25
|
</summary>
|
|
27
26
|
<div>
|
|
28
27
|
{childNodes.map((child: any) => <Astro.self node={child} />)}
|
|
@@ -31,7 +30,6 @@ const isAnc = node.isActive && !node.isCurrentPage;
|
|
|
31
30
|
) : (
|
|
32
31
|
<a
|
|
33
32
|
href={node.href}
|
|
34
|
-
class="l"
|
|
35
33
|
aria-current={node.isCurrentPage ? "page" : undefined}
|
|
36
34
|
data-a={isAnc || undefined}
|
|
37
35
|
set:html={htmlTitle}
|